<template>
  <section class="grid gap-16px grid-rows-[45fr_55fr] h-full text-white">
    <div class="bim-top grid gap-15px grid-cols-[30%_37%_33%] min-h-0">
      <!--风险场景统计-->
      <DsPanel title="风险场景统计">
        <template #default>
          <div class="flex flex-col gap-2 h-full items-end justify-between items-center">
            <div class="w-full flex flex-row items-center justify-end pr-[16px]">
              <div class="flex flex-row items-center gap-6px">
                <div class="text-14px">装置名称</div>
                <div>
                  <el-select
                    v-model="activeDevice"
                    @change="loadSafetyTrend"
                    size="small"
                    class="device-select !w-140px"
                    popper-class="logicflow-device-select"
                  >
                    <el-option v-for="device in deviceOptions" :key="device.value" :label="device.label" :value="device.value" />
                  </el-select>
                </div>
              </div>
              <!-- <div class="flex flex-row items-center">
                <div class="bg-[url('@/assets/bi_images/bim/bumen.png')] w-[78px] h-[82px] ml-[18px]"></div>
                <div class="flex flex-col ml-[5px]">
                  <div class="text-17px text-[#00B2FF] font-800 tracking-[1px]">运营四部</div>
                  <div class="text-14px">所属部门</div>
                </div>
              </div> -->
            </div>
            <div class="flex flex-row h-[110px] items-center justify-between w-full">
              <div class="w-[50%] h-full">
                <div class="relative h-full">
                  <VChart class="absolute inset-0" :option="initialChartOption" autoresize />
                </div>
              </div>
              <div class="w-[1px] h-[70%] bg-[#1E548A] mr-[10px]"></div>
              <div class="w-[50%] h-full">
                <div class="relative h-full">
                  <VChart class="absolute inset-0" :option="residualChartOption" autoresize />
                </div>
              </div>
            </div>
            <div class="flex-1 min-h-0 w-full">
              <el-table
                class="risk-table"
                :data="riskMatrixRows"
                height="100%"
                :header-cell-class-name="riskHeaderCellClass"
                :cell-style="{ background: 'transparent', color: '#fff' }"
                :header-row-class-name="() => 'risk-table__header'"
                :row-class-name="() => 'risk-table__row'"
              >
                <el-table-column prop="eventName" label="顶上事件" min-width="120" align="center" :show-overflow-tooltip="true" />
                <el-table-column prop="initEventName" label="初始事件" align="center" min-width="80" :show-overflow-tooltip="true" />
                <el-table-column prop="frequency" label="发生频率" align="center" min-width="80" />
                <el-table-column prop="initRiskLevel" label="初始风险" align="center" min-width="80">
                  <template #default="scope">
                    {{ initRiskLevelName[scope.row?.initRiskLevel] || '--' }}
                  </template>
                </el-table-column>
                <el-table-column prop="reducedRiskLevel" label="残余风险" align="center" min-width="80">
                  <template #default="scope">
                    {{ reducedRiskLevelName[scope.row?.reducedRiskLevel] || '--' }}
                  </template>
                </el-table-column>
                <el-table-column prop="result" label="后果" align="center" min-width="80" :show-overflow-tooltip="true" />
              </el-table>
            </div>
          </div>
        </template>
      </DsPanel>
      <!--风险矩阵-->
      <DsPanel title="风险矩阵">
        <template #header-extra>
          <div v-if="showTimeLine">{{ currentTimelineTime }}</div>
        </template>
        <template #default>
          <div class="flex flex-col gap-2 h-full">
            <div class="relative h-60px flex flex-row items-center justify-end">
              <div
                class="w-[200px] flex items-center gap-8px px-10px h-52px rd-6px bg-[url('@/assets/bi_images/bim/trend-btn-bg.png')] bg-cover bg-center"
              >
                <div
                  v-for="tab in matrixTabs"
                  :key="tab.value"
                  class="w-[85px] inline-flex flex-none items-center justify-center h-40px px-12px whitespace-nowrap cursor-pointer border border-transparent text-14px transition-all duration-200 text-#d8ecff"
                  :class="activeMatrixTab === tab.value ? '!text-[#30dafe] bg-[url(@/assets/bi_images/bim/trend-btn.png)] bg-cover bg-center' : ''"
                  @click="activeMatrixTab = tab.value"
                >
                  {{ tab.label }}
                </div>
              </div>
            </div>
            <div class="flex-1 min-h-0 h-full">
              <div class="flex flex-row h-full">
                <div class="relative w-[25px] pt-[20px] bg-[url('@/assets/bi_images/bim/arrow_bg_col.png')]">
                  <div class="absolute" style="writing-mode: sideways-lr">事故发生可能性</div>
                </div>
                <div class="w-[calc(100%-25px)] flex flex-col">
                  <div ref="riskGridRef" class="h-[calc(100%-25px)] w-full grid gap-1 pr-2 pl-4 pb-4" :style="gridStyle">
                    <template v-for="(row, rowIndex) in indexTrendList" :key="rowIndex">
                      <div v-for="(item, colIndex) in row" :key="`${rowIndex}-${colIndex}`" class="flex items-center justify-center">
                        <div
                          v-if="item.showValue"
                          class="text-lg font-800 relative w-full h-full cursor-pointer transition-all hover:opacity-80"
                          @click="handleMatrixCellClick(item)"
                        >
                          <div class="absolute top-[1px] top-[50%] left-[50%] translate-x--1/2 translate-y--1/2 z-1">
                            {{ item.value }}
                          </div>
                          <div class="absolute h-[18px] w-full bottom-0 bg" :class="calculateTrendBgCls(item)"></div>
                        </div>
                        <div v-else :class="calculateTrendBgCls(item)"></div>
                      </div>
                    </template>
                  </div>
                  <div class="h-[25px] w-full text-right pr-[30px] bg-[url('@/assets/bi_images/bim/arrow_bg_row.png')] bg-no-repeat bg-right">
                    事故发生严重性
                  </div>
                </div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>
      <!--风险预警-->
      <DsPanel v-if="showRiskWarning" title="风险预警">
        <template #header-extra>
          <div v-if="showTimeLine">{{ currentTimelineTime }}</div>
        </template>
        <template #default>
          <div class="flex flex-col h-full items-end justify-between">
            <div class="relative h-[65px] w-full flex flex-row items-center pr-[20px]">
              <div class="flex flex-row items-center">
                <div class="bg-[url('@/assets/bi_images/bim/shixiao.png')] w-[62px] h-[62px]"></div>
                <div class="tracking-[1px] text-[17px] ml-[5px] mr-[20px]">失效屏障</div>
                <div class="text-[18px] font-700 text-[#FF6F36]">{{ barrierInfo?.failedBarrier || 0 }}</div>
              </div>
              <div class="flex flex-row items-center ml-[50px]">
                <div class="bg-[url('@/assets/bi_images/bim/baojing.png')] w-[62px] h-[62px]"></div>
                <div class="tracking-[1px] text-[17px] ml-[5px] mr-[20px]">退化屏障</div>
                <div class="text-[18px] font-700 text-[#FFC002]">{{ barrierInfo?.degradedBarrier || 0 }}</div>
              </div>
              <div
                class="w-[102px] h-[55px] flex items-center justify-center font-700 absolute right-[20px] cursor-pointer bg-[url(@/assets/bi_images/bim/warning-btn.png)] bg-cover bg-center"
                @click="changeHideRisk"
              >
                统计
              </div>
            </div>
            <div class="flex-1 min-h-0 w-full">
              <el-table
                class="risk-table"
                :data="riskWarningRows"
                height="100%"
                :header-cell-class-name="riskHeaderCellClass"
                :cell-style="{ background: 'transparent', color: '#fff' }"
                :header-row-class-name="() => 'risk-table__header'"
                :row-class-name="() => 'risk-table__row'"
              >
                <el-table-column label="屏障状态" min-width="80" align="center">
                  <template #default="scope">
                    <div class="w-full h-[35px] z-10 relative flex items-center justify-center">
                      <div
                        class="absolute w-[45px] h-[43px] top-[10px] bg-cover bg-center"
                        :style="{ backgroundImage: scope.row.barrierStatus === 0 ? `url(${barrierDanger})` : `url(${barrierWarn})` }"
                        style="top: -13px"
                      ></div>
                      <div class="absolute top-[-5px]" :class="scope.row.barrierStatus === 0 ? 'text-[#F36F49]' : 'text-[#BE9032]'">
                        {{ riskWarningBarrierStatusName[scope.row.barrierStatus] }}
                      </div>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column prop="name" label="屏障名称" align="center" min-width="120" :show-overflow-tooltip="true" />
                <el-table-column prop="equipment" label="所属装置" align="center" min-width="80" />
                <el-table-column prop="chargePerson" label="责任人" align="center" min-width="80" />
                <el-table-column label="状态" align="center" min-width="80">
                  <template #default="scope">
                    <div class="w-full h-[35px] z-10 relative flex items-center justify-center">
                      <div
                        class="absolute w-[65px] h-[32px] top-[10px] bg-cover bg-center"
                        :style="{ backgroundImage: getStatusBackgroundImage(scope.row.status) }"
                        style="top: 2px"
                      ></div>
                      <div class="absolute top-[5px] font-700" :class="getStatusTextColor(scope.row.status)">
                        {{ riskWarningStatusName[scope.row.status] }}
                      </div>
                    </div>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
        </template>
      </DsPanel>
      <!-- 屏障异常统计 -->
      <DsPanel v-else title="屏障异常统计">
        <template #header-extra>
          <div v-if="showTimeLine">{{ currentTimelineTime }}</div>
        </template>
        <template #default>
          <div class="flex flex-col gap-2 h-full">
            <div class="relative flex items-center justify-between pl-[20px]">
              <div class="time-tabs flex items-center gap-8px px-10px h-42px rd-6px bg-[url('@/assets/bi_images/group_7767.png')] bg-cover bg-center">
                <div
                  v-for="tab in timeTabs"
                  :key="tab.value"
                  class="tab-item inline-flex flex-none items-center justify-center h-28px px-12px whitespace-nowrap cursor-pointer border border-transparent text-12px transition-all duration-200 text-#d8ecff"
                  :class="
                    activeTimeTab === tab.value
                      ? 'border-#30dafe text-#30dafe bg-[linear-gradient(180deg,rgba(48,218,254,0.22)_0%,rgba(48,218,254,0.08)_100%)] shadow-[inset_0_0_0_1px_rgba(48,218,254,0.2)]'
                      : ''
                  "
                  @click="activeTimeTab = tab.value"
                >
                  {{ tab.label }}
                </div>
              </div>
              <div
                class="w-[102px] h-[55px] flex items-center justify-center font-700 absolute right-[10px] cursor-pointer bg-[url(@/assets/bi_images/bim/warning-btn.png)] bg-cover bg-center"
                @click="changeShowRisk"
              >
                预警
              </div>
            </div>
            <div class="flex flex-col gap-25px w-full p-2 overflow-y-auto mb-[20px] hide-scrollbar">
              <div
                v-for="(item, idx) in statisticList"
                :key="idx"
                class="grid grid-cols-[auto_1fr_auto] items-center gap-16px cursor-pointer"
                @click="changeProgress(item)"
              >
                <div class="min-w-50px text-[14px]">{{ item.tag }}</div>
                <div class="stat-progress-wrap relative overflow-visible">
                  <el-progress
                    class="stat-progress"
                    :class="`stat-progress-level-${item.level}`"
                    :percentage="item.percent"
                    :stroke-width="20"
                    :show-text="false"
                  />
                  <span
                    class="progress-marker absolute top-1/2 -translate-y-1/2 w-[28px] h-[28px] border border-[#0aeefe] rounded-full pointer-events-none inline-flex items-center justify-center z-50"
                    :style="{ left: `calc(${item.percent}% - 14px)` }"
                  >
                    <span class="dot w-[10px] h-[10px] bg-white rounded-full shadow-[0_0_2px_2px_rgba(255,255,255,0.8)]"></span>
                  </span>
                </div>
                <div class="text-[#FCDC00] font-600">{{ item.percent.toFixed(2) }}%</div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>
    </div>
    <!--预警分析-->
    <div class="bim-bottom min-h-0">
      <DsPanel
        title="预警分析"
        class="relative size-full"
        :class="{
          'fullscreen-panel': isFullscreen,
          'animate__animated': isFullscreen,
          'animate__zoomIn': isFullscreen && !isExitingFullscreen,
          'animate__zoomOut': isExitingFullscreen
        }"
      >
        <template #default>
          <div class="flex flex-col h-full overflow-hidden w-full relative">
            <!-- 顶上事件选择，使用外层容器做绝对定位，避免 Element Plus 内部 position 覆盖 -->
            <div class="absolute z-10 right-0" style="top: 12px">
              <el-select v-model="activeTopEvent" size="small" class="device-select !w-160px" popper-class="logicflow-device-select">
                <el-option v-for="device in topEventOptions" :key="device.value" :label="device.label" :value="device.value" />
              </el-select>
            </div>
            <div class="flex-1 relative">
              <div class="logicflow-wrap absolute inset-0" ref="logicFlowRef" />
              <!-- 历史浮窗矩阵（右上角），颜色调暗，无数值，随时间轴基于 canyuData 标点 -->
              <div
                v-if="showTimeLine && overlayRows > 0 && overlayCols > 0"
                class="absolute z-20 right-18px top-12px rd-8px border border-[rgba(48,218,254,0.6)] shadow-[0_0_12px_rgba(48,218,254,0.25)] bg-[rgba(10,24,45,0.85)] backdrop-blur-[2px] p-10px"
                :style="{ width: overlayWidth + 'px', height: overlayHeight + 'px' }"
              >
                <div class="relative size-full">
                  <div class="flex flex-row h-full">
                    <div class="mr-2 relative w-[18px] pt-[8px] bg-[url('@/assets/bi_images/bim/arrow_bg_col.png')]">
                      <div
                        class="absolute left-0 right-0"
                        style="top: 4px; writing-mode: sideways-lr; text-align: center; font-size: 12px; color: #9ecdf6"
                      >
                        事故发生可能性
                      </div>
                    </div>
                    <div class="w-[calc(100%-18px)] flex flex-col">
                      <div class="relative h-[calc(100%-18px)] w-full">
                        <!-- 网格底图（调暗），不显示数值 -->
                        <div class="absolute inset-0 grid gap-1 pr-1" :style="overlayGridStyle">
                          <template v-for="(row, ri) in indexTrendList" :key="'ov-g-' + ri">
                            <div v-for="(cell, ci) in row" :key="'ov-g-' + ri + '-' + ci" class="relative">
                              <div class="w-full h-full opacity-70" :class="calculateTrendBgCls(cell)" />
                            </div>
                          </template>
                        </div>
                        <!-- 标点图层：使用与底图一致的 Grid（相同行列与 gap），按 x:y 直接定位到对应单元中心 -->
                        <div class="absolute inset-0 pointer-events-none grid gap-1 pr-1" :style="overlayGridStyle">
                          <div
                            v-for="(pt, i) in overlayCellPoints"
                            :key="'ov-pt-' + i"
                            class="w-full h-full flex items-center justify-center"
                            :style="{ gridRowStart: (pt.row + 1).toString(), gridColumnStart: (pt.col + 1).toString() }"
                          >
                            <span class="relative block">
                              <span class="block w-10px h-10px rd-999px bg-[#18D26B] shadow-[0_0_5px_2px_rgba(24,210,107,0.65)]"></span>
                              <span
                                class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 block w-18px h-18px rd-999px border border-[rgba(24,210,107,0.8)] opacity-80"
                              ></span>
                            </span>
                          </div>
                        </div>
                      </div>
                      <div
                        class="mt-2 h-[18px] w-full text-right pr-[20px] text-12px text-[#9ECDF6] bg-[url('@/assets/bi_images/bim/arrow_bg_row.png')] bg-no-repeat bg-right"
                      >
                        事故发生严重性
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <!-- 右下角按钮组：全屏、重置、放大、缩小（与 KeyAlarmModel 结构一致，避免被 LogicFlow 清空） -->
              <div class="absolute z-10 right-20px bottom-20px flex flex-col gap-8px">
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="toggleFullscreen"
                  :title="isFullscreen ? '退出全屏' : '全屏'"
                >
                  <svg v-if="!isFullscreen" class="w-18px h-18px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" fill="#30DAFE" />
                  </svg>
                  <svg v-else class="w-18px h-18px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" fill="#30DAFE" />
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="resetView"
                  title="重置视图"
                >
                  <svg class="w-18px h-18px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M12 2L8 6H11V11H13V6H16L12 2Z" fill="#30DAFE" />
                    <path d="M12 22L16 18H13V13H11V18H8L12 22Z" fill="#30DAFE" />
                    <path d="M2 12L6 8V11H11V13H6V16L2 12Z" fill="#30DAFE" />
                    <path d="M22 12L18 16V13H13V11H18V8L22 12Z" fill="#30DAFE" />
                    <circle cx="12" cy="12" r="2" fill="#30DAFE" />
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="zoomIn"
                  title="放大"
                >
                  <svg class="w-20px h-20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M12 5V19M5 12H19" stroke="#30DAFE" stroke-width="2" stroke-linecap="round" />
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="zoomOut"
                  title="缩小"
                >
                  <svg class="w-20px h-20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M5 12H19" stroke="#30DAFE" stroke-width="2" stroke-linecap="round" />
                  </svg>
                </button>
              </div>
            </div>
            <div class="pt-18px" v-if="showTimeLine">
              <div ref="timelineRef" class="relative h-44px mx-70px">
                <!-- 横向坐标轴底线（完整，可点击） -->
                <div
                  class="absolute left-0 right-0 h-2px bg-[#5E8DD5] translate-y--1/2 cursor-pointer"
                  :style="{ top: axisTopStyle }"
                  @click="handleTimelineClick"
                  @mousemove="handleTimelineMouseMove"
                  @mouseenter="handleTimelineMouseEnter"
                  @mouseleave="handleTimelineMouseLeave"
                ></div>
                <!-- 横向坐标轴进度线（至当前百分比） -->
                <div
                  class="absolute left-0 h-2px bg-[#5E8DD5] translate-y--1/2 pointer-events-none"
                  :style="{ width: `${timelinePercent}%`, top: axisTopStyle }"
                ></div>
                <!-- hover时显示的时间提示 -->
                <div
                  v-if="showTimelineHover"
                  class="absolute translate-x--1/2 translate-y--1/2 pointer-events-none z-20"
                  :style="{ left: `${timelineHoverPercent}%`, top: axisTopStyle }"
                >
                  <div
                    class="absolute left-1/2 bottom-[calc(100%+8px)] translate-x--1/2 text-12px whitespace-nowrap bg-[rgba(12,37,70,0.95)] border border-[#30DAFE] px-8px py-4px rd-4px"
                  >
                    {{ hoverTimelineTime }}
                  </div>
                  <!-- hover指示线 -->
                  <div class="absolute left-1/2 translate-x--1/2 w-1px h-20px bg-[#30DAFE] opacity-60" :style="{ top: '-10px' }"></div>
                </div>
                <!-- 轴线上刻度线（可点击，只在轴线上方） -->
                <template v-for="(mk, _i) in tickList" :key="_i">
                  <div
                    class="absolute w-2px bg-[#5E8DD5] opacity-70 cursor-pointer hover:opacity-100"
                    :style="{ left: `${mk.pos}%`, top: axisTopStyle, height: `${tickHeight}px`, transform: 'translateX(-50%) translateY(-100%)' }"
                    @click.stop="timelinePercent = mk.pos"
                  ></div>
                </template>
                <!-- 轴心与竖线 -->
                <div
                  class="absolute translate-x--1/2 translate-y--1/2 cursor-pointer z-10"
                  :style="{ left: `${timelinePercent}%`, top: axisTopStyle }"
                  @mousedown="startDragging"
                >
                  <div class="relative w-22px" :style="{ height: `${handleLineHeight}px` }">
                    <!-- 使用图片作竖线与圆点 -->
                    <img :src="group7769" alt="" class="absolute left-1/2 top-1/2 translate-x--1/2 translate-y--1/2 w-22px h-full" />
                    <!-- 时间标签：绝对定位到竖线顶部上方，不影响居中计算 -->
                    <div class="absolute left-1/2 bottom-[calc(100%+6px)] translate-x--1/2 text-12px whitespace-nowrap pointer-events-none">
                      {{ currentTimelineTime }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>
    </div>
  </section>
  <!-- 屏障详情弹窗 -->
  <el-dialog v-model="showDistributionLabelInfo" title="屏障详情" width="560px" append-to-body class="bi-dialog">
    <div class="analysis-content">
      <div class="row">
        <span class="label">屏障名称</span><span class="value">{{ distributionLabelInfo.nodeName || '--' }}</span>
      </div>
      <div class="row">
        <span class="label">屏障描述</span><span class="value">{{ distributionLabelInfo.description || '--' }}</span>
      </div>
      <div class="row">
        <span class="label">屏障标签</span><span class="value">{{ distributionLabelInfo.tag || '--' }}</span>
      </div>
      <div class="row">
        <span class="label">屏障类型</span><span class="value">{{ distributionLabelInfo.type || '--' }}</span>
      </div>
      <div class="row">
        <span class="label">屏障PFD</span><span class="value">{{ distributionLabelInfo.pfd ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">退化因素</span><span class="value">{{ distributionLabelInfo.degradationFactor || '--' }}</span>
      </div>
      <div class="row">
        <span class="label">屏障状态</span><span class="value">{{ distributionLabelInfo.alarmStatusLabel || '--' }}</span>
      </div>
    </div>
    <template #footer>
      <el-button type="primary" @click="showDistributionLabelInfo = false">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import 'animate.css';
import group7769 from '@/assets/bi_images/group_7769.png';
import {computed, nextTick, onMounted, onUnmounted, reactive, ref, watch} from 'vue';
import LogicFlow, {
  type EdgeConfig,
  HtmlNode,
  HtmlNodeModel,
  type NodeConfig,
  PolylineEdge,
  PolylineEdgeModel
} from '@logicflow/core';
import DsPanel from '@/views/data-screen/components/DsPanel.vue';
import type {TableHeaderCellClassName} from 'element-plus';
import VChart from 'vue-echarts';
import type {ECBasicOption} from 'echarts/types/dist/shared';
import iconYufang from '@/assets/bi_images/yufang.png';
import iconHuilu from '@/assets/bi_images/huilu.png';
import iconKekaoxing from '@/assets/bi_images/kekaoxing.png';
import iconGongyi from '@/assets/bi_images/gongy8i.png';
import iconSis from '@/assets/bi_images/sis.png';
import iconGsd from '@/assets/bi_images/gsd.png';
import iconTeshu from '@/assets/bi_images/teshu.png';
import iconRenyuan from '@/assets/bi_images/renyuan.png';
import barrierDanger from '@/assets/bi_images/bim/barrier_status_danger.png';
import barrierWarn from '@/assets/bi_images/bim/barrier_status_warn.png';
import yihuifu from '@/assets/bi_images/bim/yihuifu.png';
import weihuifu from '@/assets/bi_images/bim/weihuifu.png';
import tuihua from '@/assets/bi_images/bim/tuihua.png';
// barrier_type 系列（按 nodeType 使用 + 报警级别变体）
import bgType1 from '@/assets/bi_images/bim/barrier_type1.png';
import bgType24 from '@/assets/bi_images/bim/barrier_type24.png';
import bgType3 from '@/assets/bi_images/bim/barrier_type3.png';
import bgType5 from '@/assets/bi_images/bim/barrier_type5.png';
import bgType5Warn from '@/assets/bi_images/bim/barrier_type5_warn.png';
import bgType24_0 from '@/assets/bi_images/bim/barrier_type24_0.png';
import bgType24_rect from '@/assets/bi_images/bim/barrier_type24_rect.png';
import bgType24_0_rect from '@/assets/bi_images/bim/barrier_type24_0_rect.png';
import bgType24_1_rect from '@/assets/bi_images/bim/barrier_type24_1_rect.png';
import bgType24_2_rect from '@/assets/bi_images/bim/barrier_type24_2_rect.png';
import bgType24_3_rect from '@/assets/bi_images/bim/barrier_type24_3_rect.png';
import bgType24_1 from '@/assets/bi_images/bim/barrier_type24_1.png';
import bgType24_2 from '@/assets/bi_images/bim/barrier_type24_2.png';
import bgType24_3 from '@/assets/bi_images/bim/barrier_type24_3.png';
import {getEquipmentList} from '@/api/ds';
import {getBarrierList, getBarrierMap, getChainAlarm, getRiskMatrix, getSafetyTrend} from '@/api/barrierintegrity';
import {BarrierInfoList, BarrierList, Event, SafetyTrend} from '@/api/barrierintegrity/types';

// 按原始图片尺寸设置节点大小：预加载图片，记录自然尺寸
type ImgSize = { w: number; h: number };
const imgSizes = reactive<Record<string, ImgSize>>({});

function preloadImg(key: string, src: string) {
  const img = new Image();
  img.onload = () => {
    imgSizes[key] = { w: img.naturalWidth, h: img.naturalHeight };
    // 图片加载后，刷新一次图谱以应用最新尺寸
    scheduleLFUpdate(true);
  };
  img.src = src;
}

// 获取指定 key 的原始尺寸；未加载时回退到近似尺寸
function getOriginalSizeByKey(key: 'info' | 'rowcol' | 'danger' | 'result' | 'label', fallbackW: number, fallbackH: number) {
  const s = imgSizes[key];
  if (s && s.w > 0 && s.h > 0) {
    return { width: s.w, height: s.h };
  }
  return { width: fallbackW, height: fallbackH };
}

enum MatrixTabEnum {
  CURRENT,
  HISTORY
}

const matrixTabs = [
  { label: '当前', value: MatrixTabEnum.CURRENT },
  { label: '历史', value: MatrixTabEnum.HISTORY }
];
const activeMatrixTab = ref(MatrixTabEnum.CURRENT);

watch(
  () => activeMatrixTab.value,
  (newVal) => {
    switch (newVal) {
      case MatrixTabEnum.HISTORY:
        showTimeLine.value = true;
        // 历史矩阵，切换时刷新
        loadRiskMatrix();
        break;
      case MatrixTabEnum.CURRENT:
        showTimeLine.value = false;
        // 当前矩阵，切换时刷新
        loadRiskMatrix();
        break;
      default:
        break;
    }
  }
);

const showTimeLine = ref(false);

const changeProgress = () => {
  showTimeLine.value = true;
};

const bizBlocks = [
  {
    icon: iconYufang,
    title: '双重预防',
    headers: ['隐患总数', '重大隐患', '未整改']
  },
  {
    icon: iconHuilu,
    title: '控制回路',
    headers: ['有效自控率']
  },
  {
    icon: iconKekaoxing,
    title: '设备可靠性',
    headers: ['检修完成率', '报警总数', '一级报警']
  },
  {
    icon: iconGongyi,
    title: '工艺报警',
    headers: ['操作平稳率', '报警总数', 'A类报警']
  },
  {
    icon: iconSis,
    title: 'SIS连锁旁路',
    headers: ['旁路数量']
  },
  {
    icon: iconGsd,
    title: 'GDS报警',
    headers: ['总数', '可燃', '有毒']
  },
  {
    icon: iconTeshu,
    title: '特殊作业',
    headers: ['总数', '动火', '受限', '高处', '吊装']
  },
  {
    icon: iconRenyuan,
    title: '人员聚集',
    headers: ['红色报警', '橙色报警', '黄色报警']
  }
] as const;

const safetyTrendInitialData = computed(() => {
  return [
    { total: safetyTrendInfo.value.initLowRiskCount, detail: '低' },
    { total: safetyTrendInfo.value.initNormalRiskCount, detail: '一般' },
    { total: safetyTrendInfo.value.initMajorRiskCount, detail: '较大' },
    { total: safetyTrendInfo.value.initFatalRiskCount, detail: '重大' }
  ];
});

const safetyTrendResidualData = computed(() => {
  return [
    { total: safetyTrendInfo.value.residualLowRiskCount, detail: '低' },
    { total: safetyTrendInfo.value.residualNormalRiskCount, detail: '一般' },
    { total: safetyTrendInfo.value.residualMajorRiskCount, detail: '较大' },
    { total: safetyTrendInfo.value.residualFatalRiskCount, detail: '重大' }
  ];
});

// 饼图配色：低/较大/重大按要求覆盖，一般保持现有
const riskColorMap: Record<string, string> = {
  低: '#607bcd',
  一般: '#fff299',
  较大: '#ff8100',
  重大: '#f17172'
};
const getRiskColor = (detail: string): string => riskColorMap[detail] || '#0f40f5';

function createGradient(colors: [string, number][]): any {
  const echarts = (window as any).echarts as
    | {
        graphic: { RadialGradient: new (...args: unknown[]) => unknown };
      }
    | undefined;
  if (!echarts) {
    return {
      x: 0.5,
      y: 0.5,
      r: 34,
      colorStops: colors.map(([color, offset]) => ({ color, offset }))
    } as const;
  }
  return new echarts.graphic.RadialGradient(
    0.5,
    0.5,
    34,
    colors.map(([color, offset]) => ({ color, offset }))
  );
}

const initialChartOption = computed<ECBasicOption>(() => {
  const centerGradient = createGradient([
    ['#E0EEFF', 0],
    ['#E0EEFF', 1]
  ]);
  const title = '初始风险';
  const value = safetyTrendInfo.value.initRiskCount;
  return {
    //中间圆形的 文字位置
    title: {
      text: '{a|' + value + '}\n{c|' + title + '}',
      x: '21.5%',
      y: '32%',
      textAlign: 'center',
      textStyle: {
        rich: {
          a: {
            fontSize: 16,
            color: '#1B235B',
            fontWeight: '600'
          },
          c: {
            fontSize: 13,
            color: '#25272A',
            padding: [5, 0]
          }
        }
      }
    },
    grid: {
      left: '-120px'
    },
    tooltip: {
      trigger: 'item'
    },
    legend: {
      orient: 'horizontal',
      top: '1%',
      left: '44%',
      data: safetyTrendInitialData.value.map((item) => item.detail),
      icon: 'circle',
      formatter: (name: string) => {
        const dataAll = safetyTrendInitialData.value.map((item) => Number(item.total));
        // 修复：确保使用数字进行计算
        const total = dataAll.reduce((sum, current) => sum + current, 0);
        const itemData = safetyTrendInitialData.value.find((item) => item.detail === name);
        const value = itemData ? Number(itemData.total) : 0;
        return `{a|${name}}\n{c|${value}个}`;
      },
      textStyle: {
        rich: {
          a: {
            color: 'normal',
            fontSize: 13,
            align: 'left',
            padding: [30, 0, 0, 0]
          },
          c: {
            color: '#e1e4ea',
            fontSize: 14,
            align: 'left',
            padding: [10, 0, 0, 0]
          }
        }
      },
      selectorPosition: 'start',
      align: 'left',
      itemHeight: 30,
      itemWidth: 12,
      itemGap: 20
    },
    series: [
      {
        name: '',
        type: 'pie',
        radius: ['75%', '90%'],
        center: ['23%', '50%'],
        clockwise: false,
        avoidLabelOverlap: false,
        hoverOffset: 10,
        legendHoverLink: true,
        label: {
          normal: {
            show: false
          }
        },
        labelLine: {
          normal: {
            show: false
          }
        },
        itemStyle: {
          normal: {
            borderColor: 'rgba(255,255,255,0)'
          }
        },
        data: [
          {
            value: safetyTrendInitialData.value[0].total,
            name: safetyTrendInitialData.value[0].detail,
            itemStyle: { color: getRiskColor(safetyTrendInitialData.value[0].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendInitialData.value[1].total,
            name: safetyTrendInitialData.value[1].detail,
            itemStyle: { color: getRiskColor(safetyTrendInitialData.value[1].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendInitialData.value[2].total,
            name: safetyTrendInitialData.value[2].detail,
            itemStyle: { color: getRiskColor(safetyTrendInitialData.value[2].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendInitialData.value[3].total,
            name: safetyTrendInitialData.value[3].detail,
            itemStyle: { color: getRiskColor(safetyTrendInitialData.value[3].detail) },
            tooltip: formatterVal()
          }
        ]
      },
      {
        type: 'pie',
        radius: [0, '55%'],
        hoverAnimation: false,
        clockWise: false,
        center: ['23%', '50%'],
        tooltip: {
          formatter: title + '：' + value,
          textStyle: {
            color: '#000'
          },
          backgroundColor: '#fff',
          extraCssText: 'box-shadow: 0px 0px 10px 0px rgba(19,83,88,0.2);'
        },
        /* tooltip: { //不现实总数的提示
           show: true
       },*/
        itemStyle: {
          normal: {
            shadowBlur: 6,
            shadowColor: '#444',
            color: centerGradient
          }
        },
        label: {
          normal: {
            show: false
          }
        },
        data: [100]
      }
    ]
  };
});
const residualChartOption = computed<ECBasicOption>(() => {
  const centerGradient = createGradient([
    ['#E0EEFF', 0],
    ['#E0EEFF', 1]
  ]);
  const title = '残余风险';
  const value = safetyTrendInfo.value.residualRiskCount;
  return {
    //中间圆形的 文字位置
    title: {
      text: '{a|' + value + '}\n{c|' + title + '}',
      x: '21.5%',
      y: '32%',
      textAlign: 'center',
      textStyle: {
        rich: {
          a: {
            fontSize: 16,
            color: '#1B235B',
            fontWeight: '600'
          },
          c: {
            fontSize: 13,
            color: '#25272A',
            padding: [5, 0]
          }
        }
      }
    },
    grid: {
      left: '-120px'
    },
    tooltip: {
      trigger: 'item'
    },
    legend: {
      orient: 'horizontal',
      top: '1%',
      left: '44%',
      data: safetyTrendResidualData.value.map((item) => item.detail),
      icon: 'circle',
      formatter: (name: string) => {
        const dataAll = safetyTrendResidualData.value.map((item) => Number(item.total));
        // 修复：确保使用数字进行计算
        const total = dataAll.reduce((sum, current) => sum + current, 0);
        const itemData = safetyTrendResidualData.value.find((item) => item.detail === name);
        const value = itemData ? Number(itemData.total) : 0;
        return `{a|${name}}\n{c|${value}个}`;
      },
      textStyle: {
        rich: {
          a: {
            color: 'normal',
            fontSize: 13,
            align: 'left',
            padding: [30, 0, 0, 0]
          },
          c: {
            color: '#e1e4ea',
            fontSize: 14,
            align: 'left',
            padding: [10, 0, 0, 0]
          }
        }
      },
      selectorPosition: 'start',
      align: 'left',
      itemHeight: 30,
      itemWidth: 12,
      itemGap: 20
    },
    series: [
      {
        name: '',
        type: 'pie',
        radius: ['75%', '90%'],
        center: ['23%', '50%'],
        clockwise: false,
        avoidLabelOverlap: false,
        hoverOffset: 10,
        legendHoverLink: true,
        label: {
          normal: {
            show: false
          }
        },
        labelLine: {
          normal: {
            show: false
          }
        },
        itemStyle: {
          normal: {
            borderColor: 'rgba(255,255,255,0)'
          }
        },
        data: [
          {
            value: safetyTrendResidualData.value[0].total,
            name: safetyTrendResidualData.value[0].detail,
            itemStyle: { color: getRiskColor(safetyTrendResidualData.value[0].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendResidualData.value[1].total,
            name: safetyTrendResidualData.value[1].detail,
            itemStyle: { color: getRiskColor(safetyTrendResidualData.value[1].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendResidualData.value[2].total,
            name: safetyTrendResidualData.value[2].detail,
            itemStyle: { color: getRiskColor(safetyTrendResidualData.value[2].detail) },
            tooltip: formatterVal()
          },
          {
            value: safetyTrendResidualData.value[3].total,
            name: safetyTrendResidualData.value[3].detail,
            itemStyle: { color: getRiskColor(safetyTrendResidualData.value[3].detail) },
            tooltip: formatterVal()
          }
        ]
      },
      {
        type: 'pie',
        radius: [0, '55%'],
        hoverAnimation: false,
        clockWise: false,
        center: ['23%', '50%'],
        tooltip: {
          formatter: title + '：' + value,
          textStyle: {
            color: '#000'
          },
          backgroundColor: '#fff',
          extraCssText: 'box-shadow: 0px 0px 10px 0px rgba(19,83,88,0.2);'
        },
        /* tooltip: { //不现实总数的提示
           show: true
       },*/
        itemStyle: {
          normal: {
            shadowBlur: 6,
            shadowColor: '#444',
            color: centerGradient
          }
        },
        label: {
          normal: {
            show: false
          }
        },
        data: [100]
      }
    ]
  };
});

const formatterVal = () => {
  return {
    formatter: '{b}({d}%)',
    textStyle: {
      color: '#000'
    },
    backgroundColor: '#fff',
    extraCssText: 'box-shadow: 0px 0px 10px 0px rgba(19,83,88,0.2);'
  };
};

const showRiskWarning = ref(true);
const changeHideRisk = () => {
  showRiskWarning.value = false;
};
const changeShowRisk = () => {
  showRiskWarning.value = true;
};
const timeTabs = [
  { label: '近一小时', value: '1h' },
  { label: '近八小时', value: '8h' },
  { label: '近一天', value: '1d' },
  { label: '近一周', value: '1w' }
];
const activeTimeTab = ref(timeTabs[0].value);
// 屏障异常统计：按时间维度的模拟数据，仅显示位号与数值
// level: 0低风险 1一般风险 2较大风险 3重大风险
const barrierStatisticsData: Record<string, Array<{ tag: string; percent: number; level: number }>> = {
  '1h': [
    { tag: 'PICA970101', percent: 22.8, level: 3 },
    { tag: 'LICA-970101', percent: 19.8, level: 2 },
    { tag: 'PICA970102', percent: 19.6, level: 1 },
    { tag: 'SV-9701A/B', percent: 19.2, level: 0 },
    { tag: 'PICA970102', percent: 18.6, level: 1 }
  ],
  '8h': [
    { tag: 'PICA970101', percent: 23.8, level: 3 },
    { tag: 'LICA-970101', percent: 20.0, level: 2 },
    { tag: 'PICA970102', percent: 19.6, level: 1 },
    { tag: 'SV-9701A/B', percent: 19.0, level: 0 },
    { tag: 'PICA970102', percent: 17.6, level: 2 }
  ],
  '1d': [
    { tag: 'PICA970101', percent: 23.8, level: 3 },
    { tag: 'LICA-970101', percent: 20.0, level: 2 },
    { tag: 'PICA970102', percent: 19.6, level: 1 },
    { tag: 'SV-9701A/B', percent: 19.0, level: 0 },
    { tag: 'PICA970102', percent: 17.6, level: 1 }
  ],
  '1w': [
    { tag: 'PICA970101', percent: 23.8, level: 3 },
    { tag: 'LICA-970101', percent: 20.0, level: 2 },
    { tag: 'PICA970102', percent: 19.6, level: 1 },
    { tag: 'SV-9701A/B', percent: 19.0, level: 0 },
    { tag: 'PICA970102', percent: 17.6, level: 2 }
  ]
};

const statisticList = computed(() => barrierStatisticsData[activeTimeTab.value] || []);

enum IndexTrendItemType {
  LOW = 'low', // 蓝 #0f40f5（低）
  MEDIUM = 'medium', // 黄 #fff299（一般）
  WARNING = 'warning', // 橙 #ff8c00（较大）
  DANGER = 'danger' // 红 #951d1c（重大）
}

interface IndexTrendItem {
  type: IndexTrendItemType;
  value: number;
  showValue: boolean;
}

// 风险矩阵（来自接口）的容器
const currentMatrixGrid = ref<IndexTrendItem[][]>([]);
const historyMatrixMap = ref<Record<string, IndexTrendItem[][]>>({});
const historyLatestDate = ref<string | null>(null);

// 级别映射到显示类型：3重大→红，2较大→橙，1一般→黄，0低→蓝
function mapLevelToType(level: number): IndexTrendItemType {
  switch (level) {
    case 3:
      return IndexTrendItemType.DANGER; // 红 #951d1c
    case 2:
      return IndexTrendItemType.WARNING; // 橙 #ff8c00
    case 1:
      return IndexTrendItemType.MEDIUM; // 黄 #fff299
    case 0:
    default:
      return IndexTrendItemType.LOW; // 蓝 #0f40f5
  }
}

// 级别映射到进度条渐变颜色：3重大→红，2较大→橙，1一般→黄，0低→蓝
function mapLevelToProgressColor(level: number): string {
  switch (level) {
    case 3:
      return 'linear-gradient(90deg, #7a1716 0%, #951d1c 100%)'; // 红色渐变
    case 2:
      return 'linear-gradient(90deg, #cc7000 0%, #ff8c00 100%)'; // 橙色渐变
    case 1:
      return 'linear-gradient(90deg, #ccc27a 0%, #fff299 100%)'; // 黄色渐变
    case 0:
    default:
      return 'linear-gradient(90deg, #0c33c4 0%, #0f40f5 100%)'; // 蓝色渐变
  }
}

// 仅根据 level 生成矩阵栅格，value 置 0
// 原点统一为左下角：将 level 的行以底部为 0 进行解释，渲染时翻转为顶部为 0
function buildGridFromLevel(level: number[][]): IndexTrendItem[][] {
  const rows = level?.length || 0;
  const grid: IndexTrendItem[][] = new Array(rows);
  for (let r = 0; r < rows; r++) {
    const levelRow = level?.[r] || [];
    const cols = levelRow.length;
    const rowItems: IndexTrendItem[] = [];
    for (let c = 0; c < cols; c++) {
      const lv = Number(levelRow?.[c] ?? 0);
      rowItems.push({ type: mapLevelToType(lv), value: 0, showValue: false });
    }
    // 将数据行 r（以左下角为原点，r=0 是最底行）映射为 DOM 网格行索引（顶部为 0）
    const targetRow = rows - 1 - r;
    grid[targetRow] = rowItems;
  }
  return grid;
}

// 将某次报警记录的 initData 应用到指定网格
function clearGridCounts(grid: IndexTrendItem[][]) {
  for (const row of grid) {
    for (const cell of row) {
      cell.value = 0;
      cell.showValue = false;
    }
  }
}

type MatrixEntry = { row: number; col: number; val: number };
function parseInitData(initData?: string[] | null): MatrixEntry[] {
  const entries: MatrixEntry[] = [];
  if (!Array.isArray(initData)) return entries;
  for (const s of initData) {
    if (!s || typeof s !== 'string') continue;
    const parts = s.split(':').map((x) => Number(x));
    if (parts.length >= 3 && parts.every((n) => Number.isFinite(n))) {
      const [row, col, val] = parts as [number, number, number];
      entries.push({ row, col, val });
    }
  }
  return entries;
}

function applyCountsToGrid(grid: IndexTrendItem[][], entries: MatrixEntry[]) {
  if (!grid || grid.length === 0) return;
  clearGridCounts(grid);
  const rowsCount = grid.length;
  for (const { row, col, val } of entries) {
    if (row < 0 || col < 0) continue;
    // 原点统一为左下角：将数据行号转换为 DOM 网格索引
    const targetRow = rowsCount - 1 - row;
    if (targetRow < 0 || targetRow >= rowsCount) continue;
    if (col >= (grid[targetRow]?.length || 0)) continue;
    const cell = grid[targetRow][col];
    cell.value = Number(val) || 0;
    cell.showValue = cell.value > 0;
  }
}

// 在当前显示的矩阵上应用报警计数（优先使用已加载的 currentAlarmData）
function getDisplayedGrid(): IndexTrendItem[][] | null {
  if (activeMatrixTab.value === MatrixTabEnum.CURRENT) return currentMatrixGrid.value || null;
  const key = historyLatestDate.value;
  if (key && historyMatrixMap.value[key]) return historyMatrixMap.value[key];
  return null;
}

function getActiveRiskAlarmAt(time: Date | null, data: any[]): any | null {
  if (!Array.isArray(data) || data.length === 0) return null;
  const alarms = data
    .map((d) => d?.riskAlarm)
    .filter((a) => a && a.startTime)
    .map((a) => ({
      ...a,
      _start: new Date(a.startTime),
      _end: new Date(new Date(a.startTime).getTime() + (Number(a.duration) || 0) * 1000)
    }));
  if (alarms.length === 0) return null;
  if (!time) {
    // 无时间轴：取 startTime 最新的一条
    alarms.sort((a, b) => b._start.getTime() - a._start.getTime());
    return alarms[0];
  }
  // 有时间轴：优先取覆盖 time 的记录；若没有，取 start <= time 的最近一条
  const covering = alarms.filter((a) => time >= a._start && time <= a._end).sort((a, b) => b._start.getTime() - a._start.getTime());
  if (covering.length > 0) return covering[0];
  const before = alarms.filter((a) => a._start.getTime() <= time.getTime()).sort((a, b) => b._start.getTime() - a._start.getTime());
  return before[0] || null;
}

async function overlayMatrixCountsByContext() {
  const grid = getDisplayedGrid();
  if (!grid) return;

  // 尝试使用已加载的报警数据；若暂无，则按设备拉取一次（仅用于矩阵叠加，不改动其他状态）
  let data: any[] = currentAlarmData.value;
  if ((!data || data.length === 0) && activeDevice.value !== null) {
    try {
      const res = await getChainAlarm(activeDevice.value);
      data = res?.data || [];
    } catch {
      data = [];
    }
  }

  // 选择当前应使用的报警记录
  const now = showTimeLine.value ? getCurrentTimeFromPercent(timelinePercent.value) : null;
  const activeAlarm = getActiveRiskAlarmAt(now, data);

  // 拖动时间轴时，实时打印匹配到的报警数据（便于联调）
  if (activeAlarm && now) {
    try {
      // 打印当前百分比、时间戳与报警核心字段（避免超长日志）
      const { indicatorCode, alarmLevel, startTime, duration, canyuData, initData } = activeAlarm as any;
      // 仅截取 canyuData/initData 前若干项，避免过长
      const cd = Array.isArray(canyuData) ? canyuData.slice(0, 10) : canyuData;
      const id = Array.isArray(initData) ? initData.slice(0, 10) : initData;
      console.log('[BarrierIntegrity][TimelineMatch]', {
        percent: Number(timelinePercent.value.toFixed(2)),
        time: now.toISOString(),
        alarm: { indicatorCode, alarmLevel, startTime, duration, canyuData: cd, initData: id }
      });
    } catch {}
  }

  if (!activeAlarm) {
    clearGridCounts(grid);
    return;
  }
  const entries = parseInitData(activeAlarm.initData);
  applyCountsToGrid(grid, entries);
}

const calculateTrendBgCls = (item: IndexTrendItem) => {
  const { type } = item;
  const commonCls = ['w-full', 'h-full'];
  switch (type) {
    case IndexTrendItemType.LOW: {
      // 蓝
      commonCls.push('bg-[#0f40f5]');
      break;
    }
    case IndexTrendItemType.MEDIUM: {
      // 黄（提亮、降低饱和度以拉开与橙色的对比）
      commonCls.push('bg-[#fff299]');
      break;
    }
    case IndexTrendItemType.WARNING: {
      // 橙（加深为琥珀橙，提高与黄色的对比）
      commonCls.push('bg-[#ff8c00]');
      break;
    }
    case IndexTrendItemType.DANGER: {
      // 红
      commonCls.push('bg-[#951d1c]');
      break;
    }
    default:
      break;
  }
  return commonCls;
};
const calculateTrendBottomCls = (item: IndexTrendItem) => {
  const { type } = item;
  const cls = [];
  switch (type) {
    case IndexTrendItemType.LOW: {
      cls.push(
        // 蓝 #0f40f5
        'bg-[linear-gradient(0deg,rgba(15,64,245,1)_10%,rgba(15,64,245,0.3)_40%,rgba(15,64,245,0.2)_60%,rgba(15,64,245,0.1)_80%,rgba(15,64,245,0.05)_100%)]'
      );
      break;
    }
    case IndexTrendItemType.MEDIUM: {
      cls.push(
        // 黄 #FFF299（更浅黄，降低饱和度）
        'bg-[linear-gradient(0deg,rgba(255,242,153,1)_10%,rgba(255,242,153,0.35)_40%,rgba(255,242,153,0.22)_60%,rgba(255,242,153,0.12)_80%,rgba(255,242,153,0.06)_100%)]'
      );
      break;
    }
    case IndexTrendItemType.WARNING: {
      cls.push(
        // 橙 #FF8C00（更深琥珀橙）
        'bg-[linear-gradient(0deg,rgba(255,140,0,1)_10%,rgba(255,140,0,0.35)_40%,rgba(255,140,0,0.22)_60%,rgba(255,140,0,0.12)_80%,rgba(255,140,0,0.06)_100%)]'
      );
      break;
    }
    case IndexTrendItemType.DANGER: {
      cls.push(
        // 红 #951d1c
        'bg-[linear-gradient(0deg,rgba(149,29,28,1)_10%,rgba(149,29,28,0.3)_40%,rgba(149,29,28,0.2)_60%,rgba(149,29,28,0.1)_80%,rgba(149,29,28,0.05)_100%)]'
      );
      break;
    }
    default:
      break;
  }
  return cls;
};

// 处理风险矩阵格子点击事件
const handleMatrixCellClick = async (item: IndexTrendItem) => {
  if (!item.value) return;

  // 使用 item.value 作为 code 参数加载报警链路数据
  const code = String(item.value);
  console.log('点击风险矩阵格子，code:', code);

  // 加载报警链路数据
  if (activeDevice.value !== null) {
    await loadChainAlarm({ equipmentId: activeDevice.value, code });
  }
};

const riskWarningStatusName = {
  0: '未恢复',
  1: '已恢复',
  2: '未恢复'
};
const riskWarningBarrierStatusName = {
  0: '失效',
  1: '退化'
};

// 获取状态背景图
const getStatusBackgroundImage = (status: number) => {
  switch (status) {
    case 0: // 未恢复
      return `url(${yihuifu})`;
    case 1: // 已恢复
      return `url(${yihuifu})`;
    case 2: // 退化
      return `url(${yihuifu})`;
    default:
      return `url(${yihuifu})`;
  }
};

// 获取状态字体颜色
const getStatusTextColor = (status: number) => {
  switch (status) {
    case 0: // 未恢复
      return 'text-[#FF8A8A]';
    case 1: // 已恢复
      return 'text-[#89C3FF]';
    case 2: // 退化
      return 'text-[#FF8A8A]';
    default:
      return 'text-[#FF8A8A]';
  }
};

const initRiskLevelName = {
  0: '低风险',
  1: '一般风险',
  2: '较大风险',
  3: '重大风险'
};
const reducedRiskLevelName = {
  0: '低风险',
  1: '一般风险',
  2: '较大风险',
  3: '重大风险'
};

const riskWarningRows = computed<BarrierInfoList[]>(() => barrierInfo.value.barrierInfoList);
const riskMatrixRows = computed<Event[]>(() => safetyTrendInfo.value.events);

// el-table header cell class for colored titles
const riskHeaderCellClass: TableHeaderCellClassName = ({ columnIndex }) => {
  if (columnIndex === 1) return 'risk-hl';
  if (columnIndex === 2) return 'risk-hll';
  if (columnIndex === 3) return 'risk-lock';
  return '';
};

const barriers = [
  {
    id: 'FIC2106-1',
    name: 'FIC2106',
    gradient: 'linear-gradient(0deg, #67475c 0%, #93545e 30%, transparent 100%)',
    textColor: '#FF9C9C'
  },
  {
    id: 'FIC2106-2',
    name: 'FIC2106',
    gradient: 'linear-gradient(0deg, #41545c 0%, #87895e 30%, transparent 100%)',
    textColor: '#FFCD48'
  },
  {
    id: 'FIC2106-3',
    name: 'FIC2106',
    gradient: 'linear-gradient(0deg, #4a3f97 0%, #6346ac 30%, transparent 100%)',
    textColor: '#DAABFF'
  },
  {
    id: 'FIC2106-4',
    name: 'FIC2106',
    gradient: 'linear-gradient(0deg, #4a3f97 0%, #6346ac 30%, transparent 100%)',
    textColor: '#DAABFF'
  }
];

const activeBarrier = ref(barriers[0].id);
const barrierValues = reactive([
  { label: '开始时间', value: '2025-01-01' },
  { label: '时长', value: '1小时' },
  { label: '报警描述', value: '人为因素' },
  { label: '组合报警', value: 'XXXX/无' }
]);

const barrierInfoMap = reactive({
  [barriers[0].id]: {
    name: 'FIC206',
    startTime: '2025-01-01',
    duration: '1小时',
    description: '人为因素',
    combo: 'XXXX/无'
  },
  [barriers[1].id]: {
    name: 'FIC208',
    startTime: '2025-03-02',
    duration: '2小时',
    description: '设备震动',
    combo: 'XXXX/无'
  },
  [barriers[2].id]: {
    name: 'FIC210',
    startTime: '2025-04-16',
    duration: '3小时',
    description: '电器故障',
    combo: 'XXXX/有'
  },
  [barriers[3].id]: {
    name: 'FIC212',
    startTime: '2025-06-10',
    duration: '4小时',
    description: '外部误触',
    combo: 'XXXX/有'
  }
});
const activeBarrierInfo = computed(() => barrierInfoMap[activeBarrier.value]);
const barrierValuesStop = watch(activeBarrierInfo, (info) => {
  if (!info) return;
  barrierValues[0].value = info.startTime;
  barrierValues[1].value = info.duration;
  barrierValues[2].value = info.description;
  barrierValues[3].value = info.combo;
});
const deviceOptions = ref<Array<{ label: string; value: number }>>([]);

const activeDevice = ref<number | null>(null);
const topEventOptions = ref<Array<{ label: string; value: number }>>([]);
const activeTopEvent = ref<number | null>(null);

// 当前图谱数据（从接口加载）
const currentBarrierMapData = ref<FlowTreeNode[] | null>(null);

// 报警链路数据
const currentAlarmData = ref<any[]>([]);
const allAlarmTimes = ref<{ startTime: Date; endTime: Date }[]>([]);

const logicFlowRef = ref<HTMLDivElement | null>(null);
const timelineRef = ref<HTMLDivElement | null>(null);
let lf: LogicFlow | null = null;

const timelinePercent = ref(0); // 时间轴从起点开始
const timelineMarks = reactive<string[]>([]);
const timelineHoverPercent = ref(0); // hover位置百分比
const showTimelineHover = ref(false); // 是否显示hover提示

// 轴线位置（稍微靠下）与尺寸配置
const axisTopStyle = '56%';
const tickHeight = 10; // 刻度线高度（px）
const handleLineHeight = 38; // 手柄竖线总高度（px），以轴线为中点
const tickList = computed(() => {
  const len = timelineMarks.length;
  if (len <= 1) return [{ pos: 0, label: timelineMarks[0] ?? '' }];
  return timelineMarks.map((label, i) => ({
    label,
    pos: (i / (len - 1)) * 100
  }));
});

// 当前时间精确显示（年月日时分秒）
const currentTimelineTime = computed(() => {
  const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
  if (!currentTime) return '无数据';

  // 格式化为年月日时分秒
  return currentTime.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
});

// hover时显示的时间
const hoverTimelineTime = computed(() => {
  const hoverTime = getCurrentTimeFromPercent(timelineHoverPercent.value);
  if (!hoverTime) return '无数据';

  // 格式化为年月日时分秒
  return hoverTime.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
});

// 矩阵展示数据：根据 tab 自动切换当前/历史（历史默认取最新日期）
const indexTrendList = computed(() => {
  if (activeMatrixTab.value === MatrixTabEnum.CURRENT) {
    return currentMatrixGrid.value || [];
  }
  const key = historyLatestDate.value;
  if (key && historyMatrixMap.value[key]) {
    return historyMatrixMap.value[key];
  }
  return [];
});

// 根据数据动态设置网格的行列数
const gridStyle = computed(() => {
  const rows = indexTrendList.value?.length || 0;
  const cols = rows > 0 ? indexTrendList.value[0]?.length || 0 : 0;
  return {
    gridTemplateRows: `repeat(${rows}, 1fr)`,
    gridTemplateColumns: `repeat(${cols}, 1fr)`
  } as Record<string, string>;
});

// 浮窗矩阵网格样式与标点位置计算
const overlayRows = computed(() => indexTrendList.value?.length || 0);
const overlayCols = computed(() => (overlayRows.value > 0 ? indexTrendList.value[0]?.length || 0 : 0));
const overlayGridStyle = computed(() => ({
  gridTemplateRows: `repeat(${overlayRows.value}, 1fr)`,
  gridTemplateColumns: `repeat(${overlayCols.value}, 1fr)`
}));
// 参照原风险矩阵的单元格纵横比，浮窗整体为横向矩形
const riskGridRef = ref<HTMLDivElement | null>(null);
const overlayWidth = 420; // 固定宽度，保证横向观感
const overlayHeight = computed(() => {
  const rows = Math.max(1, overlayRows.value);
  const cols = Math.max(1, overlayCols.value);
  // 原网格单元纵横比 r = cellW/cellH，默认取 1（近似方格）
  let r = 1;
  const el = riskGridRef.value;
  if (el && el.clientWidth > 0 && el.clientHeight > 0) {
    const cellW = el.clientWidth / cols;
    const cellH = el.clientHeight / rows;
    if (cellW > 0 && cellH > 0) r = cellW / cellH;
  }
  // 目标高度 = 宽度 * (rows/cols) / r
  let h = Math.round((overlayWidth * rows) / (cols * r));
  // 约束为横向矩形：不高于宽度的 70%，不低于宽度的 40%
  const minH = Math.round(overlayWidth * 0.4);
  const maxH = Math.round(overlayWidth * 0.7);
  h = Math.max(minH, Math.min(maxH, h));
  return h;
});
// 与左/下坐标轴的内边距（像素）
type OverlayCellPoint = { row: number; col: number };
function parseCanyuData(canyuData?: string[] | string | null): Array<{ x: number; y: number }> {
  const pts: Array<{ x: number; y: number }> = [];
  if (!canyuData) return pts;
  const items: string[] = Array.isArray(canyuData)
    ? (canyuData.filter((v) => typeof v === 'string') as string[])
    : String(canyuData)
        .split(/[;,\s]+/)
        .filter(Boolean);
  for (const s of items) {
    const [xs, ys] = String(s).split(':');
    const x = Number(xs);
    const y = Number(ys);
    if (Number.isFinite(x) && Number.isFinite(y)) pts.push({ x, y });
  }
  return pts;
}

const overlayCellPoints = computed<OverlayCellPoint[]>(() => {
  const rows = overlayRows.value;
  const cols = overlayCols.value;
  if (rows <= 0 || cols <= 0) return [];

  // 选择当前上下文的报警记录（与矩阵叠加逻辑一致）
  let data: any[] = currentAlarmData.value;
  const now = showTimeLine.value ? getCurrentTimeFromPercent(timelinePercent.value) : null;
  const activeAlarm = getActiveRiskAlarmAt(now, data);
  const pts = parseCanyuData((activeAlarm as any)?.canyuData);
  if (!pts.length) return [];

  const res: OverlayCellPoint[] = [];
  for (const { x, y } of pts) {
    if (x < 0 || y < 0) continue;
    if (x >= cols) continue;
    if (y >= rows) continue;
    // 原点统一为左下角：将 y 轴从底部向上转换为网格行索引（顶部为 0）
    const rowIdx = rows - 1 - y;
    if (rowIdx < 0 || rowIdx >= rows) continue;
    res.push({ col: x, row: rowIdx });
  }
  return res;
});

const failureNum = ref(0);
const degenerationNum = ref(0);

watch(
  () => indexTrendList.value,
  (newVal) => {
    let failure = 0;
    let degeneration = 0;
    if (newVal && newVal.length > 0) {
      newVal.forEach((item) => {
        item.forEach((val) => {
          if (val.type === IndexTrendItemType.WARNING) {
            degeneration += val.value;
          }
          if (val.type === IndexTrendItemType.DANGER) {
            failure += val.value;
          }
        });
      });
      failureNum.value = failure;
      degenerationNum.value = degeneration;
    }
  },
  { immediate: true, deep: true }
);

let dragging = false;
// 限制 LogicFlow 在拖动时的刷新频率，避免卡顿
let lfUpdateTimer: number | null = null;
let lastLFUpdateTs = 0;
const MIN_LF_UPDATE_INTERVAL = 120; // 毫秒，拖动时最多 ~8 FPS 刷新图谱

function scheduleLFUpdate(immediate = false) {
  if (!lf) return;
  const now = Date.now();
  const doRender = () => {
    lfUpdateTimer && clearTimeout(lfUpdateTimer);
    lfUpdateTimer = null;
    lastLFUpdateTs = Date.now();
    // 仅刷新渲染，不重置视图
    renderLogicFlow(false);
  };

  if (immediate) {
    doRender();
    return;
  }

  const elapsed = now - lastLFUpdateTs;
  if (elapsed >= MIN_LF_UPDATE_INTERVAL) {
    doRender();
  } else if (lfUpdateTimer === null) {
    lfUpdateTimer = window.setTimeout(doRender, MIN_LF_UPDATE_INTERVAL - elapsed);
  }
}

function startDragging(event: MouseEvent) {
  dragging = true;
  document.addEventListener('mousemove', handleDragging);
  document.addEventListener('mouseup', stopDragging);
  event.preventDefault();
}

function handleDragging(event: MouseEvent) {
  if (!dragging || !timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelinePercent.value = Math.min(100, Math.max(0, percent));
  // 拖动过程中对图谱刷新进行节流
  scheduleLFUpdate(false);
}

function stopDragging() {
  dragging = false;
  document.removeEventListener('mousemove', handleDragging);
  document.removeEventListener('mouseup', stopDragging);
  // 拖动结束后立即做一次最终刷新，确保状态对齐
  scheduleLFUpdate(true);
}

// 点击时间轴定位
function handleTimelineClick(event: MouseEvent) {
  if (!timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelinePercent.value = Math.min(100, Math.max(0, percent));
  // 点击定位时即时刷新
  scheduleLFUpdate(true);
}

// 鼠标移动到时间轴时显示hover时间
function handleTimelineMouseMove(event: MouseEvent) {
  if (!timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelineHoverPercent.value = Math.min(100, Math.max(0, percent));
}

// 鼠标进入时间轴
function handleTimelineMouseEnter() {
  showTimelineHover.value = true;
}

// 鼠标离开时间轴
function handleTimelineMouseLeave() {
  showTimelineHover.value = false;
}

/**
 * 根据时间轴百分比获取当前时间
 * 将时间轴的拖动位置（0-100%）映射到实际时间范围
 */
const getCurrentTimeFromPercent = (percent: number): Date | null => {
  if (allAlarmTimes.value.length === 0) return null;

  const times = allAlarmTimes.value;
  const minTime = Math.min(...times.map((t) => t.startTime.getTime()));
  const maxTime = Math.max(...times.map((t) => t.endTime.getTime()));
  const timeRange = maxTime - minTime;

  return new Date(minTime + (timeRange * percent) / 100);
};

// 定义树形结构的数据格式（每个节点包含基础信息 + 报警信息）

enum flowClsType {
  INFO = 'info',
  ROW_CARD = 'row',
  COL_CARD = 'col',
  DANGER = 'danger',
  RESULT = 'result',
  LABEL = 'label'
}

type FlowTreeLabel = {
  id: string;
  parentId: string | null;
  title: string;
  content: string;
  x: number;
  y: number;
  clsType: flowClsType;
  // 扩展：来自 barrierMap 的原始字段
  nodeType?: number;
  nodeName?: string | null;
  description?: string | null;
  type?: any;
  pfd?: number | string | null;
  degradationFactor?: string | null;
  // 可能的标签字段
  tag?: string | null;
  code?: string | null;
  indicatorCode?: string | null;
  // 报警信息列表（来自 chainAlarm 绑定）
  alarms?: any[];
  // 标签节点标记（为 row/col 添加的下方名称节点）
  isLabel?: boolean;
};
type FlowTreeNode = FlowTreeLabel;

// LogicFlow 节点/连线 的算法
// 思路：
// - 先计算每个节点的叶子数，用于确定水平占位宽度
// - y = top + depth * vGap（同一层 y 一样）
// - x = 子树的水平区间中心点（避免交叉，结构更紧凑）
type FlowLayoutOptions = {
  nodeWidth?: number; // 仅用于计算间距，实际渲染由 FlowNodeModel 决定
  hGap?: number; // 水平或纵向层间距（取决于 orientation）
  vGap?: number; // 叶子方向的间距（取决于 orientation）
  left?: number;
  top?: number;
  orientation?: 'vertical' | 'horizontal';
};

function buildFlowFromTree(root?: FlowTreeNode[], opts: FlowLayoutOptions = {}) {
  if (!root) return { nodes: [] as any[], edges: [] as EdgeConfig[] };

  const nodes: any[] = [];
  const edges: EdgeConfig[] = [];
  root.forEach((node: FlowTreeNode) => {
    const { id, x, y, parentId, clsType } = node;
    nodes.push({ id, type: `flow-node-${clsType}`, x, y, text: { ...node } });
    if (parentId) {
      const pids = parentId.split(',');
      pids.forEach((pid) => {
        // 从 parentId (pid) 指向当前节点 (id)
        edges.push({ id: `e${pid}-${id}`, type: 'flow-edge', sourceNodeId: pid, targetNodeId: id });
      });
    }
  });
  return { nodes, edges };
}

class FlowNodeView extends HtmlNode {
  setHtml(rootEl: SVGForeignObjectElement) {
    const modelAny: any = this.props.model as any;
    const raw = (modelAny?.properties ?? {}) as any;
    // 按 nodeType 选择背景图（barrier_type 系列）；
    // 对于 row/col(2/4)，若节点在报警链路中，则按 alarmLevel 切换 barrier_type24_0/1/2/3
    const getBgByNodeType = (nodeType?: number) => {
      const getCurrentAlarmLevel = (): number | null => {
        if (!raw?.alarms || raw.alarms.length === 0) return null;
        if (showTimeLine.value) {
          const ct = getCurrentTimeFromPercent(timelinePercent.value);
          const act = ct ? getNodeAlarmAtTime(raw, ct) : null;
          if (!act) return null;
          const lv = Number(act.alarmLevel);
          return Number.isFinite(lv) ? lv : null;
        }
        const info = raw.alarms.find((a: any) => a != null) || raw.alarms[0];
        const lv = Number(info?.alarmLevel);
        return Number.isFinite(lv) ? lv : null;
      };
      const hasAlarmNow = (): boolean => {
        if (!raw?.alarms || raw.alarms.length === 0) return false;
        if (showTimeLine.value) {
          const ct = getCurrentTimeFromPercent(timelinePercent.value);
          const act = ct ? getNodeAlarmAtTime(raw, ct) : null;
          return !!act;
        }
        return raw.alarms.some((a: any) => a != null);
      };

      switch (Number(nodeType)) {
        case 1:
          return `url(${bgType1})`;
        case 2:
        case 4: {
          const lv = getCurrentAlarmLevel();
          // 标签节点用 *_rect.png，主节点用普通图
          if (raw?.isLabel) {
            if (lv === 0) return `url(${bgType24_0_rect})`;
            if (lv === 1) return `url(${bgType24_1_rect})`;
            if (lv === 2) return `url(${bgType24_2_rect})`;
            if (lv === 3) return `url(${bgType24_3_rect})`;
            return `url(${bgType24_rect})`;
          } else {
            if (lv === 0) return `url(${bgType24_0})`;
            if (lv === 1) return `url(${bgType24_1})`;
            if (lv === 2) return `url(${bgType24_2})`;
            if (lv === 3) return `url(${bgType24_3})`;
            return `url(${bgType24})`;
          }
        }
        case 3:
          return `url(${bgType3})`;
        case 5: {
          const warn = hasAlarmNow();
          return warn ? `url(${bgType5Warn})` : `url(${bgType5})`;
        }
        default:
          if (raw?.clsType === flowClsType.RESULT) return `url(${bgType5})`;
          if (raw?.clsType === flowClsType.DANGER) return `url(${bgType3})`;
          return `url(${bgType24})`;
      }
    };

    const baseClsType = `lf-flow-node--${raw?.clsType}`;
    const clsType = `${baseClsType}`;
    const escapeHtml = (s: unknown) =>
      String(s ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
    const content = String(raw?.content ?? '--');

    // row/col 主节点不再显示名称；label 节点显示名称
    const showContent = raw?.isLabel || !(raw?.clsType === flowClsType.ROW_CARD || raw?.clsType === flowClsType.COL_CARD);
    const safeLabel = showContent ? `<div class=\"lf-flow-node__content\">${escapeHtml(content)}</div>` : '';

    // 直接通过 innerHTML 设置整个节点 HTML，使用 flex 布局垂直居中
    rootEl.innerHTML = `
    <div class="lf-flow-node ${clsType}" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-image:${getBgByNodeType(raw?.nodeType)};background-size:100% 100%;background-repeat:no-repeat;">${safeLabel}</div>
    `;

    // 绑定点击事件，保持交互能力
    const el = rootEl.querySelector('.lf-flow-node') as HTMLElement | null;
    if (el) {
      el.addEventListener('click', () => {
        try {
          const { graphModel, model } = this.props as any;
          graphModel?.eventCenter?.emit?.('custom:flow-node-click', model);
        } catch {}
      });
    }
  }
}

class FlowInfoNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 初始事件（type=1）使用原图像素尺寸
    const sz = getOriginalSizeByKey('info', 256, 136);
    this.width = sz.width;
    this.height = sz.height;
  }
}

class FlowRowNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 预防性屏障（type=2）与削减性屏障（type=4）使用原图像素尺寸
    const sz = getOriginalSizeByKey('rowcol', 220, 128);
    this.width = sz.width;
    this.height = sz.height;
  }
}

class FlowColNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 预防性屏障（type=2）与削减性屏障（type=4）使用原图像素尺寸
    const sz = getOriginalSizeByKey('rowcol', 220, 128);
    this.width = sz.width;
    this.height = sz.height;
    // this.width = 127;
    // this.height = 199;
  }
}

class FlowDangerNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 顶上事件（type=3）使用原图像素尺寸
    const sz = getOriginalSizeByKey('danger', 190, 200);
    this.width = sz.width;
    this.height = sz.height;
  }
}

class FlowResultNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 后果（type=5）使用原图像素尺寸
    const sz = getOriginalSizeByKey('result', 230, 180);
    this.width = sz.width;
    this.height = sz.height;
  }
}

class FlowLabelNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    // 标签矩形，宽度与row节点一致，高度适配rect底图
    const sz = getOriginalSizeByKey('label', 220, 44);
    this.width = sz.width;
    this.height = sz.height;
  }
}

class FlowEdgeModel extends PolylineEdgeModel {
  getArrowStyle() {
    const style = super.getArrowStyle();
    return {
      ...style,
      endArrowType: 'none',
      startArrowType: 'none'
    } as any;
  }

  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#37A8FF',
      strokeWidth: 1.4
    };
  }
}

class FlowEdgeGrayModel extends PolylineEdgeModel {
  getArrowStyle() {
    const style = super.getArrowStyle();
    return {
      ...style,
      endArrowType: 'none',
      startArrowType: 'none'
    } as any;
  }

  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#9EA7B3',
      strokeWidth: 1.4
    };
  }
}

class FlowEdgeWarnModel extends PolylineEdgeModel {
  getArrowStyle() {
    const style = super.getArrowStyle();
    return {
      ...style,
      endArrowType: 'none',
      startArrowType: 'none'
    } as any;
  }

  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#FFC002',
      strokeWidth: 1.4
    };
  }
}

class FlowEdgeDangerModel extends PolylineEdgeModel {
  getArrowStyle() {
    const style = super.getArrowStyle();
    return {
      ...style,
      endArrowType: 'none',
      startArrowType: 'none'
    } as any;
  }

  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#FF3737',
      strokeWidth: 1.4
    };
  }

  setAttributes() {
    super.setAttributes();
    this.zIndex = 100; // 设置更高的 z-index 确保置顶
  }
}

const showDistributionLabelInfo = ref(false);
const distributionLabelInfo = reactive<{
  id?: string;
  nodeName?: string | null;
  description?: string | null;
  nodeType?: number | null;
  nodeTypeLabel?: string | null;
  type?: string | null;
  pfd?: number | string | null;
  degradationFactor?: string | null;
  alarmValue?: number | string | null;
  tag?: string | null;
  alarmLevel?: number | null;
  alarmStatusLabel?: string | null;
}>({});

const mapNodeTypeToLabel = (t?: number | null) => {
  switch (Number(t)) {
    case 1:
      return '初始事件';
    case 2:
      return '预防性屏障';
    case 3:
      return '顶上事件';
    case 4:
      return '削减性屏障';
    case 5:
      return '后果';
    default:
      return '--';
  }
};

const mapAlarmLevelToStatus = (lv?: number | null): string => {
  const n = Number(lv);
  if (!Number.isFinite(n)) return '--';
  switch (n) {
    case 0:
      return '无数据';
    case 1:
      return '/';
    case 2:
      return '退化屏障';
    case 3:
      return '失效屏障';
    default:
      return '--';
  }
};

function initLogicFlow() {
  if (!logicFlowRef.value) return;
  lf?.destroy();
  lf = new LogicFlow({
    container: logicFlowRef.value,
    grid: false,
    adjustNodePosition: false,
    stopZoomGraph: false,
    stopScrollGraph: false,
    stopMoveGraph: false,
    edgeTextDraggable: false,
    adjustEdgeStartAndEnd: false,
    adjustEdge: false
  });

  lf.register({
    type: 'flow-node-info',
    view: FlowNodeView,
    model: FlowInfoNodeModel
  });
  lf.register({
    type: 'flow-node-row',
    view: FlowNodeView,
    model: FlowRowNodeModel
  });
  lf.register({
    type: 'flow-node-col',
    view: FlowNodeView,
    model: FlowColNodeModel
  });
  lf.register({
    type: 'flow-node-danger',
    view: FlowNodeView,
    model: FlowDangerNodeModel
  });
  lf.register({
    type: 'flow-node-result',
    view: FlowNodeView,
    model: FlowResultNodeModel
  });
  lf.register({
    type: 'flow-node-label',
    view: FlowNodeView,
    model: FlowLabelNodeModel
  });

  lf.register({
    type: 'flow-edge',
    view: PolylineEdge,
    model: FlowEdgeModel
  });

  lf.register({
    type: 'flow-edge-gray',
    view: PolylineEdge,
    model: FlowEdgeGrayModel
  });

  lf.register({
    type: 'flow-edge-warn',
    view: PolylineEdge,
    model: FlowEdgeWarnModel
  });

  lf.register({
    type: 'flow-edge-danger',
    view: PolylineEdge,
    model: FlowEdgeDangerModel
  });

  // Listen to custom event from HtmlNode view
  lf.on('custom:flow-node-click', (model: any) => {
    try {
      const raw = (model?.properties ?? {}) as any;
      let alarmValue: number | string | null | undefined = null;
      let alarmLevel: number | null = null;
      // 屏障标签严格取自 barrierMap.nodes.tag
      const tag: string | null = raw?.tag ?? null;
      if (raw?.alarms && raw.alarms.length > 0) {
        if (showTimeLine.value) {
          const ct = getCurrentTimeFromPercent(timelinePercent.value);
          const act = ct ? getNodeAlarmAtTime(raw, ct) : null;
          alarmValue = act?.alarmValue ?? null;
          alarmLevel = Number(act?.alarmLevel);
        } else {
          const info = raw.alarms.find((a: any) => a != null) || raw.alarms[0];
          alarmValue = info?.alarmValue ?? null;
          alarmLevel = Number(info?.alarmLevel);
        }
      }

      distributionLabelInfo.id = String(raw?.id ?? '');
      distributionLabelInfo.nodeName = raw?.nodeName || raw?.title || raw?.content || '--';
      distributionLabelInfo.description = raw?.description ?? null;
      distributionLabelInfo.pfd = raw?.pfd ?? null;
      distributionLabelInfo.degradationFactor = raw?.degradationFactor ?? null;
      distributionLabelInfo.nodeType = raw?.nodeType ?? null;
      distributionLabelInfo.nodeTypeLabel = mapNodeTypeToLabel(raw?.nodeType);
      distributionLabelInfo.type = raw?.type ?? null;
      distributionLabelInfo.alarmValue = alarmValue ?? '--';
      // 屏障标签仅取 barrierMap.nodes.tag
      distributionLabelInfo.tag = tag;
      distributionLabelInfo.alarmLevel = Number.isFinite(alarmLevel as any) ? (alarmLevel as number) : null;
      distributionLabelInfo.alarmStatusLabel = mapAlarmLevelToStatus(distributionLabelInfo.alarmLevel);
      showDistributionLabelInfo.value = true;
    } catch (e) {
      showDistributionLabelInfo.value = true;
    }
  });

  renderLogicFlow();
}

function renderLogicFlow(shouldResetView = false) {
  if (!lf) return;
  lf.clearData();
  const treeData = currentBarrierMapData.value;
  if (!treeData || treeData.length === 0) return;
  const flow = buildFlowFromTree(treeData, {
    // 可按需微调布局参数
    nodeWidth: 180,
    hGap: 240,
    vGap: 160,
    left: 120,
    top: 0,
    orientation: 'horizontal'
  });

  const rawNodes = flow.nodes as any[];
  const nodes: NodeConfig[] = rawNodes.map((n) => {
    const copy: any = { ...n, properties: { ...(n as any).text } };
    if (copy.text) delete copy.text;
    return copy as NodeConfig;
  });

  // 建立节点位置与尺寸映射
  const nodeMetaMap = new Map<string, { x: number; y: number; type: string; width: number; height: number }>();

  const getNodeSize = (node: NodeConfig) => {
    const type = String(node.type || '');
    const raw = (node as any).properties || {};
    if (type === 'flow-node-info') return getOriginalSizeByKey('info', 256, 136);
    if (type === 'flow-node-row' || type === 'flow-node-col') return getOriginalSizeByKey('rowcol', 220, 128);
    if (type === 'flow-node-danger') return getOriginalSizeByKey('danger', 190, 200);
    if (type === 'flow-node-result') return getOriginalSizeByKey('result', 230, 180);
    if (type === 'flow-node-label') return getOriginalSizeByKey('label', 220, 44);
    return { width: 180, height: 120 };
  };

  nodes.forEach((node) => {
    const size = getNodeSize(node);
    nodeMetaMap.set(node.id as string, {
      x: node.x as number,
      y: node.y as number,
      type: String(node.type || ''),
      width: size.width,
      height: size.height
    });
  });

  // 为 row/col 节点添加下方标签节点（不连线）
  const labelNodes: NodeConfig[] = [];
  const LABEL_GAP = -20; // 与主节点间距：适度重叠，进一步缩小间隙
  nodes.forEach((node) => {
    const type = String(node.type || '');
    if (type === 'flow-node-row' || type === 'flow-node-col') {
      const meta = nodeMetaMap.get(node.id as string);
      if (!meta) return;
      const labelId = `${node.id}-label`;
      const labelSize = getNodeSize('flow-node-label');
      const labelY = meta.y + meta.height / 2 + LABEL_GAP + labelSize.height / 2;
      const raw = (node as any).properties || {};
      const labelProps = {
        ...raw,
        isLabel: true
      };
      const labelNode: NodeConfig = {
        id: labelId,
        type: 'flow-node-label',
        x: meta.x,
        y: labelY,
        properties: labelProps
      } as any;
      labelNodes.push(labelNode);
    }
  });
  nodes.push(...labelNodes);

  // 根据节点的 isWarn 动态生成连线类型：指向 isWarn=1 的节点使用危险连线
  const baseEdges = flow.edges || [];

  // 按源节点分组边，用于计算统一的拐点
  const edgesBySource = new Map<string, EdgeConfig[]>();
  baseEdges.forEach((edge) => {
    const sourceId = edge.sourceNodeId as string;
    if (!edgesBySource.has(sourceId)) {
      edgesBySource.set(sourceId, []);
    }
    edgesBySource.get(sourceId)!.push(edge);
  });

  // 按目标节点分组边：用于多对一时在目标侧生成统一拐点
  const edgesByTarget = new Map<string, EdgeConfig[]>();
  baseEdges.forEach((edge) => {
    const targetId = edge.targetNodeId as string;
    if (!edgesByTarget.has(targetId)) {
      edgesByTarget.set(targetId, []);
    }
    edgesByTarget.get(targetId)!.push(edge);
  });

  // 为每条边添加路径点，确保同一父节点的多条边拐点对齐
  // 获取源节点报警等级（用于边的颜色判断）
  const getNodeAlarmLevelById = (nodeId: string): number => {
    const node = rawNodes.find((n: any) => n.id === nodeId);
    const nodeData = (node as any)?.text;
    if (!nodeData) return 1; // 默认蓝色

    const supportsLevelBg =
      nodeData?.clsType === flowClsType.INFO ||
      nodeData?.clsType === flowClsType.ROW_CARD ||
      nodeData?.clsType === flowClsType.COL_CARD ||
      nodeData?.clsType === flowClsType.DANGER;

    if (!supportsLevelBg) return 1; // 仅上述四类适用

    const alarms = nodeData?.alarms || [];

    if (showTimeLine.value) {
      // 不在返回数据中：默认 1
      if (!alarms || alarms.length === 0) return 1;
      const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
      const alarm = currentTime ? getNodeAlarmAtTime(nodeData, currentTime) : null;
      // 在 alarmRes 中但未到 startTime：默认蓝色（1）
      return alarm && Number.isFinite(Number(alarm.alarmLevel)) ? Number(alarm.alarmLevel) : 1;
    } else {
      // 时间轴关闭：有返回取其 alarmLevel，否则默认 1
      if (!alarms || alarms.length === 0) return 1;
      const info = alarms.find((a: any) => a != null) || alarms[0];
      const lv = Number(info?.alarmLevel);
      return Number.isFinite(lv) ? lv : 1;
    }
  };

  // 统一使用默认连线样式，不再根据报警等级自定义颜色
  const finalEdges: EdgeConfig[] = [];

  baseEdges.map((e) => {
    const sourceId = e.sourceNodeId as string;
    const targetId = e.targetNodeId as string;
    const sourceMeta = nodeMetaMap.get(sourceId);
    const targetMeta = nodeMetaMap.get(targetId);

    // 如果源节点或目标节点位置不存在，直接返回
    if (!sourceMeta || !targetMeta) {
      const edge = { ...e, type: 'flow-edge' } as EdgeConfig;
      finalEdges.push(edge);
      return edge;
    }

    // 获取同一源节点/同一目标节点的所有边
    const siblingEdges = edgesBySource.get(sourceId) || [];
    const targetSiblings = edgesByTarget.get(targetId) || [];

    // 计算连线的起止锚点（从源节点右侧中点到目标节点左侧中点）
    const start = { x: sourceMeta.x + sourceMeta.width / 2, y: sourceMeta.y };
    const end = { x: targetMeta.x - targetMeta.width / 2, y: targetMeta.y };

    // 如果源节点有多个子节点，添加统一的拐点列，使多条边在列上对齐
    if (siblingEdges.length > 1) {
      // 统一拐点列的 x（取起止锚点的中点，避免穿过节点）
      const bendX = start.x + (end.x - start.x) * 0.5;

      const pointsList = [
        { x: start.x, y: start.y }, // 从源节点边界出发
        { x: bendX, y: start.y }, // 水平到统一拐点列
        { x: bendX, y: end.y }, // 垂直到目标 y
        { x: end.x, y: end.y } // 水平进入目标节点边界
      ];

      const edge = { ...e, type: 'flow-edge', pointsList } as EdgeConfig;
      finalEdges.push(edge);
      return edge;
    }

    // 多对一：在目标左侧预留拐点列，避免直线
    if (targetSiblings.length > 1) {
      const preEnterOffset = 40; // 距离目标左边界的预留距离
      const bendX = end.x - preEnterOffset;
      const pointsList = [
        { x: start.x, y: start.y },
        { x: bendX, y: start.y },
        { x: bendX, y: end.y },
        { x: end.x, y: end.y }
      ];

      const edge = { ...e, type: 'flow-edge', pointsList } as EdgeConfig;
      finalEdges.push(edge);
      return edge;
    }

    // 单子节点：直接从源右侧到目标左侧，绘制直折线（两点）
    const pointsList = [
      { x: start.x, y: start.y },
      { x: end.x, y: end.y }
    ];

    const edge = { ...e, type: 'flow-edge', pointsList } as EdgeConfig;
    finalEdges.push(edge);
    return edge;
  });
  lf.render({ nodes, edges: finalEdges });
  if (shouldResetView) {
    setTimeout(() => {
      resetView();
    }, 100);
  }
}

// 重置视图到合适位置和缩放
function resetView() {
  if (!lf) return;
  try {
    // 全屏状态优先自适应以包含全部节点
    if (isFullscreen.value) {
      fitAllToView();
      return;
    }

    // 非全屏时回到默认视图
    lf.resetZoom();
    lf.resetTranslate();
  } catch (error) {
    console.error('重置视图失败:', error);
  }
}

// 自适应缩放：使所有节点在容器内可见（保留边距）
function fitAllToView(padding = 40) {
  if (!lf || !logicFlowRef.value) return;
  const treeData = currentBarrierMapData.value;
  if (!treeData || treeData.length === 0) return;

  // 使用当前树数据构建一次节点列表（包含坐标与类型）
  const flow = buildFlowFromTree(treeData, {
    nodeWidth: 180,
    hGap: 240,
    vGap: 160,
    left: 120,
    top: 0,
    orientation: 'horizontal'
  });
  const rawNodes: any[] = (flow.nodes as any[]) || [];
  if (!rawNodes.length) return;

  const getSizeByType = (type: string) => {
    if (type === 'flow-node-info') return getOriginalSizeByKey('info', 256, 136);
    if (type === 'flow-node-row' || type === 'flow-node-col') return getOriginalSizeByKey('rowcol', 220, 128);
    if (type === 'flow-node-danger') return getOriginalSizeByKey('danger', 190, 200);
    if (type === 'flow-node-result') return getOriginalSizeByKey('result', 230, 180);
    if (type === 'flow-node-label') return getOriginalSizeByKey('label', 220, 44);
    return { width: 180, height: 120 };
  };

  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  rawNodes.forEach((n) => {
    const type = String(n?.type || '');
    const { width, height } = getSizeByType(type);
    const x = Number(n.x);
    const y = Number(n.y);
    const left = x - width / 2;
    const right = x + width / 2;
    const top = y - height / 2;
    const bottom = y + height / 2;
    if (left < minX) minX = left;
    if (right > maxX) maxX = right;
    if (top < minY) minY = top;
    if (bottom > maxY) maxY = bottom;
  });

  if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) return;

  const bboxW = Math.max(1, maxX - minX);
  const bboxH = Math.max(1, maxY - minY);

  const cw = logicFlowRef.value.clientWidth || 0;
  const ch = logicFlowRef.value.clientHeight || 0;
  if (cw <= 0 || ch <= 0) return;

  const scaleX = (cw - padding * 2) / bboxW;
  const scaleY = (ch - padding * 2) / bboxH;
  let targetScale = Math.min(scaleX, scaleY) * 0.98; // 略留边距
  targetScale = Math.max(0.3, Math.min(2.5, targetScale));

  const { SCALE_X, TRANSLATE_X, TRANSLATE_Y } = (lf as any).getTransform?.() || ({} as any);
  const currentScale = Number(SCALE_X || 1);
  if (currentScale <= 0) return;

  const ratio = targetScale / currentScale;
  if (Math.abs(ratio - 1) > 0.001) {
    (lf as any).zoom?.(ratio);
  }

  const centerX = (minX + maxX) / 2;
  const centerY = (minY + maxY) / 2;
  const scale = targetScale;
  const newTX = cw / 2 - centerX * scale;
  const newTY = ch / 2 - centerY * scale;
  const dx = newTX - Number(TRANSLATE_X || 0);
  const dy = newTY - Number(TRANSLATE_Y || 0);
  (lf as any).translate?.(dx, dy);
}

// 全屏状态管理与操作
const isFullscreen = ref(false);
const isExitingFullscreen = ref(false);
const toggleFullscreen = () => {
  if (isFullscreen.value) {
    // 退出全屏
    isExitingFullscreen.value = true;
    setTimeout(() => {
      isFullscreen.value = false;
      isExitingFullscreen.value = false;
    }, 500);
  } else {
    // 进入全屏
    isFullscreen.value = true;
    // 等待容器尺寸稳定后，自适应缩放以展示全部节点
    nextTick(() => {
      setTimeout(() => {
        try {
          fitAllToView();
        } catch {}
      }, 300);
    });
  }
};

// 放大/缩小
function zoomIn() {
  if (!lf) return;
  try {
    const transform = (lf as any).getTransform?.() || ({} as any);
    const currentZoom = Number(transform.SCALE_X || 1);
    if (currentZoom < 2.5) {
      (lf as any).zoom?.(1.2);
    }
  } catch (error) {
    console.error('放大失败:', error);
  }
}

function zoomOut() {
  if (!lf) return;
  try {
    const transform = (lf as any).getTransform?.() || ({} as any);
    const currentZoom = Number(transform.SCALE_X || 1);
    if (currentZoom > 0.5) {
      (lf as any).zoom?.(0.8);
    }
  } catch (error) {
    console.error('缩小失败:', error);
  }
}

watch(activeTopEvent, async (newEventId) => {
  if (newEventId !== null) {
    // 加载屏障图谱
    await loadBarrierMap(newEventId);
    // 加载报警链路数据（不传code，默认加载当前设备的所有报警）
    if (activeDevice.value !== null) {
      await loadChainAlarm({ equipmentId: activeDevice.value });
    }
  }
});

watch(activeDevice, async (newDevice) => {
  if (newDevice !== null) {
    await loadSafetyTrend(newDevice);
    await loadRiskMatrix();
  }
});

// 监听时间轴拖动，重新渲染节点
// 在非拖动场景下也做一次刷新（点击、tick 跳转等），拖动时的刷新已由 scheduleLFUpdate 节流
watch(timelinePercent, async () => {
  if (!dragging) scheduleLFUpdate(true);
  if (showTimeLine.value) {
    await overlayMatrixCountsByContext();
  }
});

// 风险场景统计
const safetyTrendInfo = ref<SafetyTrend>({});
const loadSafetyTrend = async (equipmentId: number) => {
  const res = await getSafetyTrend(equipmentId);
  const data = res.data;
  safetyTrendInfo.value = data;

  // 加载顶上事件选项
  if (data.events && data.events.length > 0) {
    topEventOptions.value = data.events.map((event: Event) => ({
      label: event.eventName,
      value: event.eventId
    }));
    // 默认选中第一个事件
    if (activeTopEvent.value === null) {
      activeTopEvent.value = topEventOptions.value[0].value;
    }
  }
};
// 风险预警
const barrierInfo = ref<BarrierList>({});
const loadBarrierList = async () => {
  const res = await getBarrierList();
  barrierInfo.value = res.data;
};
// 风险矩阵
const loadRiskMatrix = async () => {
  const type = activeMatrixTab.value;
  const res = await getRiskMatrix(type);
  const data = res?.data;

  if (type === MatrixTabEnum.CURRENT) {
    // 当前：仅使用 level 渲染底色；count 改由 chainAlarm.initData 叠加
    const level = data?.level || [];
    currentMatrixGrid.value = buildGridFromLevel(level);
    // 尝试叠加一次当前上下文的报警计数
    await overlayMatrixCountsByContext();
  } else {
    // 历史：仅使用 level 渲染底色；count 改由 chainAlarm.initData 叠加
    const map: Record<string, IndexTrendItem[][]> = {};
    let latestKey: string | null = null;
    let latestTs = -Infinity;
    if (data && typeof data === 'object') {
      Object.keys(data).forEach((k) => {
        const entry = data[k];
        if (entry && entry.level) {
          map[k] = buildGridFromLevel(entry.level);
          const ts = Date.parse(k);
          if (!Number.isNaN(ts) && ts > latestTs) {
            latestTs = ts;
            latestKey = k;
          }
        }
      });
    }
    historyMatrixMap.value = map;
    historyLatestDate.value = latestKey;
    // 叠加一次当前上下文的报警计数
    await overlayMatrixCountsByContext();
  }
};

/**
 * 生成时间轴刻度（固定为 0~24 小时，每小时一个刻度）
 * 规则：
 * - 若有有效报警，取最早报警所在日期的 00:00 作为起点，范围为该日 0~24 点
 * - 若无报警，默认使用“今天”0~24 点
 */
const generateTimelineMarks = (alarmData: any[]) => {
  // 计算基准起点（当天 00:00）
  let baseStart: Date;
  const validStarts: number[] = [];
  if (Array.isArray(alarmData) && alarmData.length > 0) {
    alarmData.forEach((item: any) => {
      if (item?.riskAlarm?.startTime) {
        const ts = new Date(item.riskAlarm.startTime).getTime();
        if (!Number.isNaN(ts)) validStarts.push(ts);
      }
    });
  }

  if (validStarts.length > 0) {
    const earliest = new Date(Math.min(...validStarts));
    baseStart = new Date(earliest.getFullYear(), earliest.getMonth(), earliest.getDate(), 0, 0, 0, 0);
  } else {
    const now = new Date();
    baseStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
  }

  const baseEnd = new Date(baseStart.getTime() + 24 * 60 * 60 * 1000);

  // 用固定 0~24 小时区间作为时间轴映射范围
  allAlarmTimes.value = [{ startTime: baseStart, endTime: baseEnd }];

  // 生成 25 个刻度（0..24 小时）
  const marks: string[] = [];
  for (let h = 0; h <= 24; h++) {
    marks.push(`${String(h).padStart(2, '0')}:00`);
  }
  timelineMarks.splice(0, timelineMarks.length, ...marks);
};

/**
 * 将报警信息挂载到节点数据上
 */
const mountAlarmsToNodes = (nodes: FlowTreeNode[], alarmMap: Map<number, any[]>) => {
  nodes.forEach((node) => {
    const nodeId = parseInt(node.id);
    node.alarms = alarmMap.get(nodeId) || [];
  });
};

/**
 * 加载报警链路数据
 * @param params - { equipmentId?: number, code?: string }
 */
const loadChainAlarm = async (params: { equipmentId?: number; code?: string }) => {
  try {
    const alarmRes = await getChainAlarm(params.equipmentId, params.code);
    const alarmData = alarmRes.data || [];

    // 不过滤数据，所有返回的节点都标记为报警状态
    currentAlarmData.value = alarmData;

    // 创建 nodeId -> alarms 的映射
    // 所有返回的 nodeId 都应该被标记为报警节点，即使 riskAlarm 为 null
    const alarmMap = new Map<number, any[]>();
    alarmData.forEach((item: any) => {
      const nodeId = item.nodeId;
      if (!alarmMap.has(nodeId)) {
        alarmMap.set(nodeId, []);
      }
      // 即使 riskAlarm 为 null 也添加，表示该节点在报警链路中
      alarmMap.get(nodeId)!.push(item.riskAlarm);
    });

    // 清空之前的报警信息
    if (currentBarrierMapData.value) {
      currentBarrierMapData.value.forEach((node) => {
        node.alarms = [];
      });
    }

    // 将报警信息挂载到节点上
    if (currentBarrierMapData.value) {
      mountAlarmsToNodes(currentBarrierMapData.value, alarmMap);
    }

    // 如果通过code查询，则显示时间轴
    if (params.code) {
      showTimeLine.value = true;
      // 只使用有效的报警数据生成时间轴
      const validAlarms = alarmData.filter((item: any) => item.riskAlarm !== null);
      generateTimelineMarks(validAlarms);
      // 在“当前”矩阵下，点击栅格出现时间轴时，默认定位到最后一个报警点（按 startTime 最大）
      if (activeMatrixTab.value === MatrixTabEnum.CURRENT && validAlarms.length > 0) {
        try {
          const starts: number[] = validAlarms
            .map((it: any) => (it?.riskAlarm?.startTime ? new Date(it.riskAlarm.startTime).getTime() : NaN))
            .filter((t: number) => Number.isFinite(t));
          if (starts.length > 0 && allAlarmTimes.value.length > 0) {
            const latestStart = Math.max(...starts);
            const minStart = Math.min(...allAlarmTimes.value.map((t) => t.startTime.getTime()));
            const maxEnd = Math.max(...allAlarmTimes.value.map((t) => t.endTime.getTime()));
            const range = Math.max(1, maxEnd - minStart);
            const percent = ((latestStart - minStart) / range) * 100;
            timelinePercent.value = Math.min(100, Math.max(0, percent));
          }
        } catch {}
      }
    } else {
      showTimeLine.value = false;
    }

    // 渲染流程图并重置视图
    renderLogicFlow(true);
    // 根据当前上下文（是否有时间轴/当前时间）叠加更新风险矩阵的计数
    await overlayMatrixCountsByContext();
  } catch (error) {
    console.error('加载报警链路数据失败:', error);
  }
};

/**
 * 根据当前时间获取节点的报警状态
 * 如果节点在当前时间有激活的报警，返回该报警信息
 * 支持一个节点有多个报警，返回第一个在当前时间范围内的报警
 */
const getNodeAlarmAtTime = (node: FlowTreeNode, currentTime: Date) => {
  if (!node.alarms || node.alarms.length === 0) return null;

  // 找出在当前时间激活的报警（报警时间范围：startTime ～ startTime + duration）
  for (const alarm of node.alarms) {
    if (!alarm || !alarm.startTime) continue;
    const startTime = new Date(alarm.startTime);
    const duration = alarm.duration || 0; // 持续时间，单位：秒
    const endTime = new Date(startTime.getTime() + duration * 1000);

    if (currentTime >= startTime && currentTime <= endTime) {
      return alarm;
    }
  }
  return null;
};

// 预警分析
const loadBarrierMap = async (eventId: number) => {
  try {
    const res = await getBarrierMap(eventId);
    const data = res.data || { nodes: [], relations: [] };

    // 转换数据结构：从 API 的 nodes + relations 转换为 FlowTreeNode[]
    const nodes = data.nodes || [];
    const relations = data.relations || [];

    // nodeType 到 clsType 的映射
    const nodeTypeToClsType = (nodeType: number): flowClsType => {
      switch (nodeType) {
        case 1:
          return flowClsType.INFO; // 始发事件
        case 2:
          return flowClsType.ROW_CARD; // 预防性屏障
        case 3:
          return flowClsType.DANGER; // 顶上事件
        case 4:
          return flowClsType.COL_CARD; // 削减性屏障
        case 5:
          return flowClsType.RESULT; // 后果
        default:
          return flowClsType.ROW_CARD; // 默认
      }
    };

    // 构建节点映射
    const nodeMap = new Map<number, any>();
    nodes.forEach((node: any) => {
      nodeMap.set(node.nodeId, node);
    });

    // 计算入度和出度
    const inDegree = new Map<number, number>();
    const outDegree = new Map<number, number>();
    const incomingEdges = new Map<number, number[]>(); // 记录每个节点的所有来源节点
    const outgoingEdges = new Map<number, number[]>(); // 记录每个节点的所有目标节点

    nodes.forEach((node: any) => {
      inDegree.set(node.nodeId, 0);
      outDegree.set(node.nodeId, 0);
      incomingEdges.set(node.nodeId, []);
      outgoingEdges.set(node.nodeId, []);
    });

    relations.forEach((rel: any) => {
      const source = rel.nodeFrom;
      const target = rel.nodeTo;
      inDegree.set(target, (inDegree.get(target) || 0) + 1);
      outDegree.set(source, (outDegree.get(source) || 0) + 1);
      incomingEdges.get(target)?.push(source);
      outgoingEdges.get(source)?.push(target);
    });

    // 使用拓扑排序计算每个节点的层级（用于布局）
    const levels = new Map<number, number>();
    const queue: number[] = [];

    // 找出所有起点（入度为0的节点）
    inDegree.forEach((degree, nodeId) => {
      if (degree === 0) {
        queue.push(nodeId);
        levels.set(nodeId, 0);
      }
    });

    // BFS 计算每个节点的层级
    while (queue.length > 0) {
      const current = queue.shift()!;
      const currentLevel = levels.get(current) || 0;

      relations.forEach((rel: any) => {
        if (rel.nodeFrom === current) {
          const target = rel.nodeTo;
          const newLevel = currentLevel + 1;
          if (!levels.has(target) || levels.get(target)! < newLevel) {
            levels.set(target, newLevel);
          }
          const targetInDegree = inDegree.get(target) || 0;
          inDegree.set(target, targetInDegree - 1);
          if (inDegree.get(target) === 0) {
            queue.push(target);
          }
        }
      });
    }

    // 计算最大层级
    let maxLevel = 0;
    levels.forEach((level) => {
      if (level > maxLevel) maxLevel = level;
    });

    // 按层分组节点
    const nodesByLevel = new Map<number, number[]>();
    levels.forEach((level, nodeId) => {
      if (!nodesByLevel.has(level)) {
        nodesByLevel.set(level, []);
      }
      nodesByLevel.get(level)!.push(nodeId);
    });

    // 收集所有 nodeType=5 的节点（后果节点），用于右对齐
    const resultNodes: number[] = [];
    nodes.forEach((node: any) => {
      if (node.nodeType === 5) {
        resultNodes.push(node.nodeId);
      }
    });

    // 转换为 FlowTreeNode[]
    const treeNodes: FlowTreeNode[] = [];
    const hGap = 280; // 层间距（横向间距）
    const vGap = 200; // 节点间距（增大分支间的纵向间距）
    const left = 120; // 左侧起点
    const top = 60; // 顶部起点

    // 存储每个节点的计算位置，用于单父节点对齐
    const nodePositions = new Map<number, { x: number; y: number }>();

    // 为每个节点计算其父节点的平均y坐标（重心）
    const nodeBarycenter = new Map<number, number>();

    // 按层级从小到大遍历，确保父节点先于子节点被处理
    for (let level = 0; level <= maxLevel; level++) {
      let nodesInLevel = nodesByLevel.get(level) || [];

      // 对于非第一层的节点，根据父节点位置排序以减少交叉
      if (level > 0) {
        // 计算每个节点的重心（父节点y坐标的平均值）
        nodesInLevel.forEach((nodeId) => {
          const parentIds = incomingEdges.get(nodeId) || [];
          if (parentIds.length > 0) {
            const parentYs = parentIds.map((pid) => nodePositions.get(pid)?.y).filter((y): y is number => y !== undefined);
            if (parentYs.length > 0) {
              const avgY = parentYs.reduce((sum, y) => sum + y, 0) / parentYs.length;
              nodeBarycenter.set(nodeId, avgY);
            }
          }
        });

        // 根据重心对节点排序
        nodesInLevel = [...nodesInLevel].sort((a, b) => {
          const baryA = nodeBarycenter.get(a) ?? Number.MAX_SAFE_INTEGER;
          const baryB = nodeBarycenter.get(b) ?? Number.MAX_SAFE_INTEGER;
          return baryA - baryB;
        });

        // 更新排序后的节点列表
        nodesByLevel.set(level, nodesInLevel);
      }

      nodesInLevel.forEach((nodeId, indexInLevel) => {
        const node = nodeMap.get(nodeId);
        if (!node) return;

        const totalInLevel = nodesInLevel.length;

        // 根据接口返回的 nodeType 确定节点类型
        const clsType = nodeTypeToClsType(node.nodeType || 1);

        // 获取父节点信息
        const parentIds = incomingEdges.get(nodeId) || [];

        // 计算位置
        let x: number;
        let y: number;

        if (node.nodeType === 5) {
          // nodeType=5 的节点右对齐，使用统一的 x 坐标
          x = left + (maxLevel + 1) * hGap - 150;
          // 计算后果节点在所有后果节点中的索引
          const resultIndex = resultNodes.indexOf(nodeId);
          const totalResults = resultNodes.length;
          // 垂直居中分布
          y = top + resultIndex * vGap + (totalResults > 1 ? 0 : vGap / 2);
        } else if (parentIds.length === 1) {
          // 只有一个父节点
          x = left + level * hGap;
          const parentId = parentIds[0];
          const parentOutDegree = outDegree.get(parentId) || 0;

          if (parentOutDegree === 1) {
            // 父节点只有一个子节点，y 坐标与父节点保持一致
            const parentPos = nodePositions.get(parentId);
            y = parentPos ? parentPos.y : top + indexInLevel * vGap;
          } else {
            // 父节点有多个子节点，按层级布局分散开
            y = top + indexInLevel * vGap + (totalInLevel > 1 ? 0 : vGap / 2);
          }
        } else {
          // 其他节点按层级布局
          x = left + level * hGap;
          y = top + indexInLevel * vGap + (totalInLevel > 1 ? 0 : vGap / 2);
        }

        // 保存当前节点位置
        nodePositions.set(nodeId, { x, y });

        // 构建父节点ID列表（多个父节点用逗号分隔）
        const parentId = parentIds.length > 0 ? parentIds.join(',') : null;

        treeNodes.push({
          id: String(nodeId),
          parentId,
          title: node.nodeName || '',
          content: node.nodeName || '',
          x,
          y,
          clsType,
          nodeType: node.nodeType,
          nodeName: node.nodeName,
          description: node.description ?? null,
          type: node.type ?? null,
          pfd: node.pfd ?? null,
          degradationFactor: node.degradationFactor ?? null,
          // 屏障标签仅保留 barrierMap.nodes.tag
          tag: (node as any).tag ?? null
        });
      });
    }

    currentBarrierMapData.value = treeNodes;
    renderLogicFlow(true);
  } catch (error) {
    console.error('加载屏障图谱失败:', error);
  }
};
// 加载设备列表
const loadDeviceList = async () => {
  try {
    const res = await getEquipmentList();
    const data = res.data || [];
    deviceOptions.value = data.map((item: any) => ({
      label: item.sbmc,
      value: item.id
    }));
    // 默认选中第一项
    if (deviceOptions.value.length > 0 && activeDevice.value === null) {
      activeDevice.value = deviceOptions.value[0].value;
      // 加载节点图谱和报警数据
      await loadSafetyTrend(activeDevice.value);
    }
  } catch (error) {
    console.error('加载设备列表失败:', error);
  }
};

onMounted(() => {
  loadDeviceList();
  loadBarrierList();
  loadRiskMatrix();
  initLogicFlow();
  // 预加载各类型底图以便获取自然尺寸
  preloadImg('info', bgType1);
  preloadImg('rowcol', bgType24);
  preloadImg('danger', bgType3);
  preloadImg('result', bgType5);
  preloadImg('label', bgType24_rect);
});

onUnmounted(() => {
  lf?.destroy();
  barrierValuesStop();
  document.removeEventListener('mousemove', handleDragging);
  document.removeEventListener('mouseup', stopDragging);
  if (lfUpdateTimer) {
    clearTimeout(lfUpdateTimer);
    lfUpdateTimer = null;
  }
});
</script>

<style scoped lang="scss">
.analysis-content {
  display: grid;
  grid-auto-rows: minmax(28px, auto);
  row-gap: 10px;
  color: #fff;
  padding: 0 20px;
}

.analysis-content .row {
  display: grid;
  grid-template-columns: 90px 1fr;
  column-gap: 12px;
  align-items: center;
}

.analysis-content .label {
  color: #8ad2ff;
}

.analysis-content .value {
  color: #ffffff;
}

.bim-bottom :deep(.ds-panel__content) {
  padding: 0;
}

/* 全屏样式（与 KeyAlarmModel 保持一致） */
.fullscreen-panel {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  z-index: 9999 !important;
  background: rgba(8, 28, 60, 0.98) !important;
  margin: 0 !important;
  padding: 20px !important;
  box-sizing: border-box !important;
  --animate-duration: 0.5s;
}

.fullscreen-panel :deep(.ds-panel__content) {
  height: calc(100% - 50px) !important;
}

// LogicFlow 组件样式
:deep(.lf-flow-node) {
  padding: 0; /* 使用原图尺寸，不叠加内边距 */
  background: transparent no-repeat center / 100% 100%;
  border: none; /* 所有节点取消边框 */
  border-radius: 0; /* 避免裁切圆角，保持原图形状 */
  color: #ffffff;
  font-size: 12px;
  line-height: 1.4;
  text-align: center;
  box-shadow: none; /* 移除阴影，避免看起来像边框 */
  white-space: normal;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

:deep(.lf-flow-node--info) {
  padding: 20px 15px 30px;
  text-align: center;
  background-image: url('@/assets/bi_images/bim/barrier_type1.png');
}

:deep(.lf-flow-node--row) {
  padding: 15px;
  background-image: url('@/assets/bi_images/bim/barrier_type24.png');
}

:deep(.lf-flow-node--col) {
  padding: 10px;
  background-image: url('@/assets/bi_images/bim/barrier_type24.png');
}

:deep(.lf-flow-node--danger) {
  background-image: url('@/assets/bi_images/bim/barrier_type3.png');
  padding: 20px;
}

:deep(.lf-flow-node--result) {
  background-image: url('@/assets/bi_images/bim/barrier_type5.png');
  font-weight: 600;
}

// 报警节点样式：替换背景图，不做边框/动画
:deep(.lf-flow-node--warn) {
  border-color: inherit !important;
  border-width: inherit !important;
  box-shadow: none !important;
  animation: none !important;
}

/* 已移除基于 alarmLevel 的节点背景图逻辑，改为仅按 nodeType 设置 */

:deep(.lf-flow-node__inner) {
  white-space: pre-line;
}

// 新的节点内部排版样式
:deep(.lf-flow-node__title) {
  position: absolute;
  top: 10px;
  left: 10px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__content) {
  font-size: 14px;
  line-height: 1.3;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__meta) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
  line-height: 1.2;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__bars) {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  height: 12px;
}

:deep(.lf-flow-node__bar) {
  display: block;
  height: 12px;
  border-radius: 2px;
}

:deep(.lf-flow-node__bar.bar-1) {
  background: rgba(53, 126, 175, 0.31);
  border: 1px solid #1b89c8;
}

:deep(.lf-flow-node__bar.bar-2) {
  background: rgba(209, 207, 91, 0.3);
  border: 1px solid #c89b2f;
}

:deep(.lf-flow-node__bar.bar-3) {
  background: #e54f21;
  border: 1px solid #a23311;
}

:deep(.lf-flow-node__bar.bar-4) {
  background: rgba(171, 52, 239, 0.31);
  border: 1px solid #6c2fc8;
}

// Element Plus select 组件样式覆盖
// 下拉面板样式
:deep(.logicflow-device-select) {
  background: rgba(12, 37, 70, 0.9) !important;
}

:deep(.logicflow-device-select .el-select-dropdown__item) {
  background: rgba(12, 37, 70, 0.9) !important;
  color: #ffffff !important;
}

:deep(.logicflow-device-select .el-select-dropdown__item:hover) {
  background: rgba(48, 124, 188, 0.4) !important;
}

// 选择框本体样式（联锁报警头部 el-select）
:deep(.device-select .el-select__wrapper) {
  height: 40px !important;
  min-height: 40px !important;
  background: url('@/assets/bi_images/rect_1718.png') no-repeat center / 100% 100% !important;
  border: none !important;
  box-shadow: none !important;
  color: #fff;
  padding: 0 12px !important;
  text-align: left !important;
}

:deep(.device-select .el-select__placeholder) {
  text-align: left !important;
  padding-left: 5px !important;
}

:deep(.device-select .el-select__selection) {
  align-items: center; /* 垂直居中内容 */
  text-align: center;
  font-size: 14px;
}

/* 调小下拉箭头与后缀区域尺寸 */
:deep(.device-select .el-select__suffix),
:deep(.device-select .el-select__caret) {
  font-size: 14px;
}

/* 下拉面板的字号也略微减小，保持一致性 */
:deep(.logicflow-device-select .el-select-dropdown__item) {
  font-size: 14px !important;
}

// Make LogicFlow canvas transparent and leave space for timeline
.bim-bottom :deep(.lf-graph) {
  background: transparent !important;
}

/* 缩放按钮样式（与 KeyAlarmModel 保持一致） */
.zoom-btn {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.zoom-btn:hover {
  box-shadow: 0 0 12px rgba(48, 218, 254, 0.5);
}

.zoom-btn:active {
  transform: scale(0.95);
}

.bim-top :deep(.risk-table) {
  background: transparent;
  color: #fff;
}

.bim-top :deep(.risk-table .el-table__inner-wrapper) {
  background: transparent;
}

.bim-top :deep(.risk-table__header) .el-table__cell {
  background: rgba(10, 38, 74, 0.95) !important;
  color: #fff !important;
  font-weight: 700;
}

.bim-top :deep(.risk-table__row .el-table__cell) {
  background: rgba(12, 40, 70, 0.8) !important;
  border-bottom: 1px solid rgba(48, 124, 188, 0.4) !important;
}

.bim-top :deep(.risk-table .el-table__row:hover > td) {
  background: rgba(18, 60, 98, 0.9) !important;
}

/* 风险统计进度条样式 */
.bim-top :deep(.stat-progress) {
  width: 100%;
}

.bim-top :deep(.stat-progress .el-progress-bar__outer) {
  background: #28446d;
  border-radius: 1px;
  display: flex !important;
  align-items: center !important;
}

.bim-top :deep(.stat-progress .el-progress-bar__inner) {
  background: linear-gradient(90deg, #1486db 0%, #00f0ff 100%);
  border-radius: 1px;
  height: 40%;
  top: 50%;
  transform: translateY(-50%);
}

.bim-top .stat-progress-wrap {
  position: relative;
  overflow: visible;
}

.bim-top .stat-progress-wrap > .progress-marker {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border: 1px solid #0aeefe;
  border-radius: 50%;
  box-sizing: border-box;
  pointer-events: none;
  z-index: 100;
}

.bim-top .stat-progress-wrap > .progress-marker > .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 2px 2px rgba(255, 255, 255, 0.8);
}

.hide-scrollbar {
  &::-webkit-scrollbar {
    display: none;
  }
}
</style>
